2

I've searched for the answer to this but according to many guides and SO questions this code still appears correct to me, yet it runs synchronously.

private void CheckConditions()
{
    foreach (var obj in myObjects)
    {
        if (obj.ConditionMet)
        {
            HandleConditionAsync(obj);
        }
    }
    DoOtherWork();
}

private async void HandleConditionAsync(MyObject obj)
{
    // shouldn't control transfer back to CheckConditions() here while we wait for user input?
    string userInput = await obj.MessagePromptAsync("hello user");
    DoSomeBookkeeping(obj);
}

// (MyObject.cs)
private MessagePrompt messagePrompt; // inherits from UserControl
public async Task<string> MessagePromptAsync(string prompt)
{
    return await Task.FromResult<string>(messagePrompt.Prompt(prompt));
}

// (MessagePrompt.cs)
public string Prompt(string prompt)
{
    this.UIThread(() => this.SetMessagePrompt(prompt));
    userInputAutoResetEvent.WaitOne();
    return myResult; // set in a button handler that also sets the AutoResetEvent
}

I'm intending for CheckConditions() to continue along merrily but instead it is stuck on MessagePrompt's AutoResetEvent despite my async/awaits. The only thing I can figure might be wrong is that perhaps MessagePrompt's methods aren't able to run asynchronously due to some restriction from UserControl, its use of a UI thread reference, or maybe non-async methods at the top of the stack.

pelotron
  • 95
  • 3
  • 13
  • 1
    Please note that providing [MCVE] is recommended for "debug my code" posts. In this case you should have narrowed down to "Why following code runs synchronously: `await Task.FromResult(messagePrompt.Prompt(prompt));` ", or maybe even `await Task.FromResult("test");`. Otherwise people may try to address other unrelated mistakes/confusing bits of code. – Alexei Levenkov Aug 31 '16 at 22:46
  • If I knew within this stack that my problem was in this bit of code, I wouldn't have needed to post this question. – pelotron Sep 01 '16 at 16:06
  • This is the whole point of trying to get [MCVE] - if one actually spends time trying to remove unrelated parts of the code from sample in most cases problem either becomes obvious or at least significantly easier to search for/get answer. – Alexei Levenkov Sep 01 '16 at 17:10

2 Answers2

4

There's nothing in your code that's asynchronous. The only task you have, you created from a result value, meaning the Prompt() method has to complete and return its result before you'll even get the Task object back to wait on. That object will already be completed, so any await on it will complete immediately, once it has the Task to wait on.

Maybe you meant this instead:

public async Task<string> MessagePromptAsync(string prompt)
{
    return await Task.Run(() => messagePrompt.Prompt(prompt));
}

Or alternatively (if you really do have nothing else in the MessagePromptAsync() method):

public Task<string> MessagePromptAsync(string prompt)
{
    return Task.Run(() => messagePrompt.Prompt(prompt));
}

Note that this may lead to a different problem, depending on what DoOtherWork() and UIThread() actually do. If your UI thread gets tied up in DoOtherWork() and the UIThread() method is wrapping Dispatcher.Invoke() or similar, then you'll have a deadlock.

If that does not address your problem, please provide a good Minimal, Complete, and Verifiable code example that reliably reproduces the problem.

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • From what I understand Task.Run() sends your method to a thread pool to get executed. What if I wanted this code to run on one thread? – pelotron Sep 01 '16 at 13:46
  • _"What if I wanted this code to run on one thread?"_ -- then it wouldn't be asynchronous. A single thread can only do one thing at a time. If you want the calling thread to be able to do anything else while you're waiting on the prompt, then you have to use another thread (whether via `Task.Run()` or any other mechanism). If you want it all to run in one thread, then get rid of all the `async`, `Task`, `await`, `AutoResetEvent`, etc. and just run it all in one thread. – Peter Duniho Sep 01 '16 at 14:33
  • The [MSDN docs](https://msdn.microsoft.com/en-us/library/hh156528.aspx) state _"An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task."_ To me the context implies that a Task may or may not be a separate thread. The [definition of Task](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx) says _"Represents an asynchronous operation."_ which could mean ".NET magic on one thread" for all I know. – pelotron Sep 01 '16 at 16:00
  • _"a Task may or may not be a separate thread"_ -- yes, you are right; that is absolutely correct. A `Task` may or may not even represent code that executes, never mind does it tell you anything about which thread that code executes on. But, that's different from asking what if you _wanted_ the code to run on one thread. If you are in fact executing code, and that code is in fact executed entirely in a single thread, and that thread is the code that initiated the execution of that code, then by definition that wouldn't be asynchronous. – Peter Duniho Sep 01 '16 at 16:12
1

You need to make CheckConditions() async as well, and then await the call to HandleConditionAsync(MyObject obj). CheckConditions() runs synchronously in your sample.

private async Task CheckConditionsAsync()
{
    foreach (var obj in myObjects)
    {
        if (obj.ConditionMet)
        {
            await HandleConditionAsync(obj);
        }
    }
    DoOtherWork();
}

Also, and this is just a best practices thing, an async method should always return a Task when possible. The only time I've ever had to use async void is for compatibility with an event handler. You can see I've changed CheckConditions() this way, and HandleConditionAsync(MyObject obj) should be modified similarly. I also changed the method name to represent it's asynchronous behaviour.

If you need to run a method that returns a Task synchronously (and you shouldn't do this, this is an indication of something incorrect about your design), you can run it with Task.FromResult(MyMethodAsync()). Again, avoid doing this wherever you can, it defeats the purpose of making a method asynchronous to in the first place.

Eric Sondergard
  • 565
  • 5
  • 22
  • Also, see [this](http://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously?rq=1) post for a proper way to run asynchronous methods synchronously. Sometimes, you just have to do it. – Eric Sondergard Aug 31 '16 at 22:33
  • 1
    While this is mostly valid information by itself, this does not address problem shown in the question - no really asynchronous methods. Consider either answering question or deleting the post. – Alexei Levenkov Aug 31 '16 at 22:43
  • Wouldn't this lead to a chain of compiler warnings that would have me change every function in the stack to async? If an async function is not awaited we get a warning, but to await it you must make the calling function async... – pelotron Sep 01 '16 at 14:16