2016-08-31 160 views
2

我已经搜索了这个答案,但根据很多指南和SO问题,这段代码对我来说仍然显示正确,但它同步运行。为什么异步/等待不会异步运行?

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 
} 

我打算为CheckConditions()欢快继续沿而是还停留在MessagePrompt的的AutoResetEvent尽管我异步/等待。我唯一能想到的可能是错误的是,由于UserControl的某些限制,它使用UI线程引用,或者可能是堆栈顶部的非异步方法,MessagePrompt的方法可能无法异步运行。

+1

请注意,提供[MCVE]建议用于“调试我的代码”帖子。在这种情况下,你应该缩小为“为什么以下代码同步运行:'等待Task.FromResult (messagePrompt.Prompt(提示));'”,或者甚至可以等待Task.FromResult (“test”); 。否则,人们可能会试图解决其他无关的错误/混淆的代码位。 –

+0

如果我知道在这个堆栈中,我的问题是在这段代码中,我不需要发布这个问题。 – pelotron

+0

这是试图获得[MCVE]的全部观点 - 如果在大多数情况下,实际上花费时间尝试从样本中删除不相关部分的代码,问题会变得明显,或者至少显着更容易搜索/获取答案。 –

回答

4

你的代码中没有什么是异步的。您从结果值创建的唯一任务,即从Prompt()方法创建的方法必须完成并返回其结果,然后才能使对象返回等待状态。该对象已经完成,所以其中的任何await将立即完成,一旦它有Task等待。

也许你的意思是这个:

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

或替代地(如果你真的有没有别的在MessagePromptAsync()法):

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

注意,这可能会导致不同的问题,取决于DoOtherWork()UIThread()实际上做了什么。如果你的UI线程在DoOtherWork()中被占用,并且UIThread()方法包装Dispatcher.Invoke()或类似的,那么你将有一个死锁。

如果这样不能解决您的问题,请提供一个很好的Minimal, Complete, and Verifiable code example,以便可靠地重现问题。

+0

从我所理解的Task.Run()发送你的方法到一个线程池来执行。如果我想让这个代码在一个线程上运行,该怎么办? – pelotron

+0

_“如果我想让这个代码在一个线程上运行,该怎么办?” - 然后它不会是异步的。一个线程一次只能做一件事。如果您希望调用线程在等待提示时能够执行其他任何操作,则必须使用另一个线程(无论是通过“Task.Run()”还是其他任何机制)。如果你希望所有的代码都在一个线程中运行,那么可以删除所有的'async','Task','await','AutoResetEvent'等等,然后在一个线程中运行它们。 –

+0

[MSDN文档](https://msdn.microsoft.com/zh-cn/library/hh156528.aspx)state _“一个await表达式不会阻塞它正在执行的线程,而是会导致编译器将异步方法的其余部分注册为对等待任务的延续。“对我而言,上下文暗示任务可能是也可能不是单独的线程。 [任务定义](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx)表示_“表示异步操作。”_这可能意味着“.NET魔术在一条线上“,就我所知。 – pelotron

1

您还需要使CheckConditions()异步,然后等待HandleConditionAsync(MyObject obj)的呼叫。 CheckConditions()在您的示例中同步运行。

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

另外,这只是一个最佳实践的事情,异步方法应该总是在可能的情况下返回任务。我唯一需要使用async void是为了与事件处理程序兼容。你可以看到我已经改变CheckConditions()这种方式,并且HandleConditionAsync(MyObject obj)应该被类似地修改。我还更改了方法名称以表示它的异步行为。

如果您需要运行同步返回Task的方法(并且不应该这样做,这表示关于您的设计有些不正确),那么可以使用Task.FromResult(MyMethodAsync())运行它。再次,避免在任何可能的情况下执行此操作,它首先会破坏使方法异步的目的。

+0

此外,请参阅[this](http://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously?rq=1)发布适当的方式来运行异步方法同步。有时候,你只需要这样做。 –

+1

尽管这本身大部分是有效的信息,但这并没有解决问题中显示的问题 - 没有真正的异步方法。考虑回答问题或删除帖子。 –

+0

这不会导致一连串的编译器警告,让我将堆栈中的每个函数都改为异步吗?如果一个异步函数没有被等待,我们会得到一个警告,但要等待它,你必须使调用函数异步... – pelotron