1

I thought I understood async/await but clearly not. I have something like this:

public async void SomeEventHandler()
{
    await Task.Run(() => Method1());
}

private async Task Method1()
{
    // Running on a worker thread here.

    await SomeOtherMethodAsync();

    // Now running on the UI thread.
}

As you can see from the above comments, Method1 starts out by running on a b/g thread as you would expect, having been called using Task.Run(). However after the first await, the remaining code then runs on the UI thread. I believe that adding ConfigureAwait(false) to the "inner" async method call will prevent this from happening, but I don't understand the reason why it resumed on the UI thread in the first place. Why the UI thread and not the original (or other) b/g thread?

Surely "defaulting" to resuming on the UI thread is counter-productive, as there is potential to block the UI, which is exactly what async/await is trying to avoid.

In my real code, Method1 does a lot of CPU-bound stuff, hence why I call it using Task.Run; the method also calls numerous async I/O bound methods. Am I taking the right approach with my use of async/await and Task.Run?

Edit Just to clarify, I used breakpoints within Method1, and the Threads window, to see what thread the code was running on. Before the awaitable method call the Threads window highlighted a worker thread, while after the awaitable method call the "main thread" was highlighted.

Edit2 In response to @Aly's answer below, using the same debug text, this is what I see:

ManagedThreadID (Dispatcher): 11
ManagedThreadID (MainThread - before Task.Run(Method1)): 11
ManagedThreadID (Method1 - before SomeOtherMethodAsync): 14
ManagedThreadID (SomeOtherMethodAsync): 14
ManagedThreadID (Method1 - after SomeOtherMethodAsync): 11
ManagedThreadID (MainThread - after Task.Run(Method1)): 18
Andrew Stephens
  • 9,413
  • 6
  • 76
  • 152
  • 5
    "Now running on the UI thread." -- no it isn't. It can't be. There's no SynchronizationContext present for the `await` on the line above to capture. I just tried it in a WPF app and confirmed it. – canton7 Feb 20 '19 at 12:45
  • 3
    @PanagiotisKanavos no, it's behaviour which doesn't happen. The `await` in question doesn't have an original synchronization context to resume on. Code after the `await` in `SomeEventHandler` is a different story, but that's not the question he's asking. Please try the code in question before saying it does what the OP says it does! – canton7 Feb 20 '19 at 12:47
  • `ConfigureAwait(false)` allows continuations to resume on another thread but that does not guarantee that it will. – Crowcoder Feb 20 '19 at 12:47
  • @canton7 I was refering to the outer await. The question mixes up various concepts and expectations – Panagiotis Kanavos Feb 20 '19 at 12:49
  • @PanagiotisKanavos the question is about the *inner* await however. Any answer will be interpreted in that context - he will assume your answer relates to the *inner* await. – canton7 Feb 20 '19 at 12:49
  • 1
    @PanagiotisKanavos He says "Method1 starts out by running on a b/g thread as you would expect, having been called using Task.Run(). However after the first await, the remaining code then runs on the UI thread" which is fairly unambiguous IMO. And you said that's the expected behaviour, which is wrong. This is backed up by the code comments in his sample. – canton7 Feb 20 '19 at 12:52
  • 1
    @PanagiotisKanavos the comment "// Now running on the UI thread." is wrong. The sentence "However after the first await [in Method1], the remaining code then runs on the UI thread" is wrong. You keep coming back to the outer `await` in `SomeEventHandler`, but that was never part of the question. – canton7 Feb 20 '19 at 12:54
  • 7
    @AndrewStephens I think you've simplified your problem too far, and you've ended up with something that doesn't demonstrate your issue. Your assertion that the code at the comment "// Now running on the UI thread." runs on the UI thread is incorrect, and you can see this with a simple test app. Please update your question with a sample which actually reproduces your issue. – canton7 Feb 20 '19 at 13:02
  • @canton7 the pseudo-code demonstrates what is happening in my "real" application. Breakpoints within the method being called show it running on a worker thread initially, then on the main thread after the first of the awaitable methods. – Andrew Stephens Feb 20 '19 at 13:14
  • 1
    Can you try to create a [mcve]? As @canton7 suggests, what you've shown doesn't demonstrate the issue & what you're suggesting shouldn't be possible. – Charles Mager Feb 20 '19 at 13:16
  • 1
    @AndrewStephens I just ran your exact code in a sample application, with those exact breakpoints, and it did not match your description of what happens (although it did match what *should* happen). – canton7 Feb 20 '19 at 13:17
  • @canton7 Unfortunately I can't provide a self-contained working example as this is part of a large system that interacts with external hardware. However at least now I know that it shouldn't be behaving like this - now I just need to figure out why it is. – Andrew Stephens Feb 20 '19 at 13:22
  • @AndrewStephens Have a look over my answer and try to `Debug.WriteLine` your thread IDs. Maybe the UI is being blocked due to some other operation. – Aly Elhaddad Feb 20 '19 at 13:26
  • This is will help in your case. https://stackoverflow.com/questions/20052995/async-await-seems-to-use-the-ui-thread – Eman Hamed Feb 20 '19 at 13:32
  • @AndrewStephens in your edit, are you using Aly El-Haddad's code exactly, or did you put his debugging statements into your code? – canton7 Feb 20 '19 at 14:04
  • 1
    Also, it's odd that your dispatcher is thread 11 - normally it's thread 1. Which UI framework are you using, and are you *sure* that you start on the dispatcher thread? – canton7 Feb 20 '19 at 14:12
  • @canton I put his debug statements in my real application, keeping the "fake" pseudo-code method names to avoid further confusion. It's a WPF app - apologies for not mentioning this in the question. – Andrew Stephens Feb 20 '19 at 14:18
  • 2
    @AndrewStephens right, so again the problem is in code that we can't see, and you won't share with us. The output means nothing, because it's output from some black box which contains the problem, but is impossible for us to inspect. If you'd posted that output from the sample code *in your question*, that would be different. As it stands, though, the sample code is still useless because it doesn't demonstrate the issue. – canton7 Feb 20 '19 at 14:21

1 Answers1

1

Just like @canton7 pointed in comments, I've just tested the code provided on a WPF app and the behavior you described doesn't seem to actually exist.

My tests:

public async void SomeEventHandler()
{
    Debug.WriteLine($"ManagedThreadID (Dispatcher): {Dispatcher.CurrentDispatcher.Thread.ManagedThreadId}");
    Debug.WriteLine($"ManagedThreadID (MainThread - before Task.Run(Method1)): {Thread.CurrentThread.ManagedThreadId}");
    await Task.Run(() => Method1());
    Debug.WriteLine($"ManagedThreadID (MainThread - after Task.Run(Method1)): {Thread.CurrentThread.ManagedThreadId}");
}
private static async Task Method1()
{
    Debug.WriteLine($"ManagedThreadID (Method1 - before SomeOtherMethodAsync): {Thread.CurrentThread.ManagedThreadId}");
    await SomeOtherMethodAsync();
    Debug.WriteLine($"ManagedThreadID (Method1 - after SomeOtherMethodAsync): {Thread.CurrentThread.ManagedThreadId}");
}
private static Task SomeOtherMethodAsync()
{
    Debug.WriteLine($"ManagedThreadID (SomeOtherMethodAsync): {Thread.CurrentThread.ManagedThreadId}");
    return Task.Delay(1000);
}

Debug output:

ManagedThreadID (Dispatcher): 1
ManagedThreadID (MainThread - before Task.Run(Method1)): 1
ManagedThreadID (Method1 - before SomeOtherMethodAsync): 3
ManagedThreadID (SomeOtherMethodAsync): 3
ManagedThreadID (Method1 - after SomeOtherMethodAsync): 3
ManagedThreadID (MainThread - after Task.Run(Method1)): 1
Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31