2

So I'm trying to get a driver working in a WinForms application (I wish I could go WPF but that is not an option here) and I'm running into an interesting problem. The UI thread blocks forever when an await method is called in the driver. Here is a very simplified version of my problem, again this only seems to happen on Windows Forms applications.

private void button_Click(object sender, EventArgs e)
{
    MessageBox.Show(this.TestSynchronous().ToString());
}

// I'm trying to avoid having to change the code below
private int TestSynchronous()
{
    return this.TestAsync().Result;
}

private async Task<int> TestAsync()
{
    await Task.Delay(100);

    var rand = new Random();

    return rand.Next();
}

Is there a way I can wrap the call to the 'TestSynchronous' method to prevent it from permanently blocking the UI thread? Remember I'm trying to avoid having to change the 'Test' methods in any way.

I've seen similar posts like duplicate like await works but calling task.Result hangs/deadlocks which are similar to my problem but my problem cannot be solved the same way. The reason being he has direct access to the async method being awaited whilst I do not. So to reiterate, I'm calling into a library that only presents me with the 'TestSynchronous' method and I want to know if there's any way I can wrap the method so it doesn't end up blocking somewhere down into the library where I don't have access to change the code.

Community
  • 1
  • 1
Thermonuclear
  • 352
  • 3
  • 18
  • Since it is well known and easy to search for (https://www.bing.com/search?q=c%23+async+result+deadlock) issue I'm not sure how you've missed it during your research. For future question make sure to add results of your research into the posts (lack of *demonstrated* research may cause downvotes on the posts irrespective of amount of days you've spent investigating the problem). – Alexei Levenkov Sep 29 '16 at 23:24
  • Please read my updated posted above. You're not wrong that it is essentially the same problem occurring but due to my own specific limitations the solutions outlined in the post cannot solve my problem. – Thermonuclear Sep 29 '16 at 23:40

2 Answers2

10

The UI thread blocks forever when an await method is called in the driver.

You're running into a common deadlock problem that I describe in full on my blog. In short, await captures a "context" and uses that to resume its async method. WinForms has a SynchronizationContext that resumes on the UI thread. When you block the UI thread by calling Result, the async method cannot resume and thus there is a deadlock.

Is there a way I can wrap the call to the 'TestSynchronous' method to prevent it from permanently blocking the UI thread?

There is no solution that works in every scenario.

The best approach is to allow async to naturally grow through your codebase:

private async Task<int> TestNoLongerSynchronousAsync()
{
  return await this.TestAsync();
}

private async void button_Click(object sender, EventArgs e)
{
  MessageBox.Show((await this.TestNoLongerSynchronousAsync()).ToString());
}

Regardless of how hard you think this will be, it is almost certainly much easier than doing a sync-over-async hack.

If you can't use await (for a very strong definition of "can't"), then you can use one of the hacks described in my brownfield async article:

  • Blocking (already tried, didn't work).
  • Blocking on a thread pool thread (i.e., Task.Run(() => this.TestAsync()).Result).
  • Pumping a nested message loop (the most dangerous of all).

None of these work in all scenarios. There are some scenarios where there are no solutions. Good luck.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Since OP said that other answer of yours is not helpful (which is essentially "run operation in null synchronization context") I'm not sure this would help :) – Alexei Levenkov Sep 30 '16 at 00:35
  • No this is helpful, not the answer I was hoping for but very informative. Thank you, I may have to push to get the code base changed then but I'll probably play with it a little longer just to verify one of the other methods don't work for me. – Thermonuclear Sep 30 '16 at 15:23
  • @Stephen Cleary never ceases to amaze. Thanks for the informative response. – joelc Jul 16 '20 at 22:35
2

So I was actually able to solve this problem and the answer was actually pretty simple. Here is the modified version of the example.

private void button_Click(object sender, EventArgs e)
{
    var task = Task.Run<int>(
            () => this.TestSynchronous());

    task.Wait();

    MessageBox.Show(task.Result.ToString());
}

There are other methods to do effectively the same thing but this appears work for my problem. The funny thing is I had previously (before posting my question) attempted to fix it using a similar method and it didn't work but it does now so I don't know what I did wrong.

Thermonuclear
  • 352
  • 3
  • 18