6

Trying to figure out why this code hangs. I can remove any one of the 3 lines at the bottom of the test and it won't hang, but all 3 together makes it hang. Any help would be greatly appreciated!

[Fact]
public async Task CanAddValuesInParallel() {
    var muxer = ConnectionMultiplexer.Connect("localhost");
    var db = muxer.GetDatabase();

    await AddAsync(db, "test", "1");
    await db.KeyDeleteAsync("test");

    Task.Run(() => AddAsync(db, "test", "1")).Wait();
}

public async Task<bool> AddAsync(IDatabase db, string key, string value) {
    return await db.StringSetAsync(key, value, null, When.NotExists);
}
Mike Cole
  • 14,474
  • 28
  • 114
  • 194
Eric J. Smith
  • 961
  • 8
  • 20
  • Why do you use `Task.Run` and why `Wait()`? – i3arnon Dec 02 '14 at 21:12
  • 1
    I think problem in `Task.Run(() => AddAsync(db, "test", "1")).Wait();`. Here you have deadlock. – Hamlet Hakobyan Dec 02 '14 at 21:13
  • This is the simplified version of my code. Trying to break it down to be as simplified as possible. I am trying to understand what is going on. – Eric J. Smith Dec 02 '14 at 21:14
  • Apparently I am running into this issue: https://github.com/StackExchange/StackExchange.Redis/issues/88 – Eric J. Smith Dec 02 '14 at 21:41
  • @EricJ.Smith: xUnit has a bad habit of providing a `SynchronizationContext` for all its test methods. This can cause a number of problems when mixing synchronous and asynchronous code. I suspect you're seeing some combination of problems I describe in two blog posts [here](http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) and [here](http://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html). – Stephen Cleary Dec 02 '14 at 21:48
  • @StephenCleary This happens in nunit as well. – Blake Niemyjski Dec 02 '14 at 22:01
  • @BlakeNiemyjski: For `async Task` unit test methods, or just `async void` ones? – Stephen Cleary Dec 02 '14 at 22:08
  • I haven't tried async void only async Task because I've had issues with async void unit tests :D – Blake Niemyjski Dec 02 '14 at 22:16

1 Answers1

13

It sounds to me like a sync-context deadlock from mixing Wait and await. Which is why you never do that - (switching into "Gilbert and Sullivan"): well, hardly ever!

If it helps, I suspect that removing the await in the Wait subtree will fix it - which should be trivial since that tree can be replaced with a trivial pass-thru:

public Task<bool> AddAsync(IDatabase db, string key, string value) {
    return db.StringSetAsync(key, value, null, When.NotExists);
}

The important point here is that SE.Redis bypasses sync-context internally (normal for library code), so it shouldn't have the deadlock.

But ultimately: mixing Wait and await is not a good idea. In addition to deadlocks, this is "sync over async" - an anti-pattern.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900