2016-04-01 202 views
15

我有以下方法:如何从非异步方法调用异步方法?

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do stuff **/ 
     var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
     return returnedResult; 
    } 


    private async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = await httpClient.GetStringAsync(SourceURI); 
      return json; 
     } 
    } 

以上不工作,似乎无法正常返回任何结果。我不知道我在哪里错过了一个强制等待结果的声明?我想RetrieveHolidayDatesFromSource()方法返回一个字符串。

下面的工作正常,但它是同步的,我相信它可以改进?请注意,以下是我想要更改为异步的同步,但由于某种原因无法将头部缠绕。

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do Stuff **/ 

     var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/ 
     return returnedResult; 
    } 


    private string RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = httpClient.GetStringAsync(SourceURI); 
      return json.Result; 
     } 
    } 

我错过了什么吗?

注意:由于某种原因,当我断开上面的Async方法时,当它到达“var json = await httpClient.GetStringAsync(SourceURI)”行时,它刚刚离开断点,我不能回到方法。

回答

23

我错过了什么吗?

是的。异步代码 - 就其性质而言,意味着当前线程在操作正在进行时未被使用。同步代码 - 就其性质而言,意味着当前线程在操作过程中被阻塞。这就是为什么从字面上调用异步代码的异步代码甚至没有意义。实际上,正如我在我的博客上描述的那样,a naive approach (using Result/Wait) can easily result in deadlocks

首先要考虑的是:应该我的API是同步还是异步?如果它处理I/O(如本例中),则它为should be asynchronous。因此,这将是一个更合适的设计:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync(); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); /** Where result gets used **/ 
    return returnedResult; 
} 

正如我在async best practices article描述,你应该去“异步一路”。如果你不这样做,你不会从异步中获得任何好处,所以为什么要麻烦呢?

但是,让我们说,你有兴趣最终去异步,但是现在你不能改变一切,你只是想改变你的应用程序的一部分。这是一个非常普遍的情况。

在这种情况下,正确的做法是暴露两个同步和异步API。最终,在所有其他代码升级之后,可以删除同步API。我在article on brownfield async development中探索了这种场景的各种选项;我个人最喜欢的是“布尔参数黑客”,这将是这样的:

public string RetrieveHolidayDatesFromSource() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult(); 
} 

public Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false); 
} 

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) { 
    var result = await this.GetHolidayDatesAsync(sync); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); 
    return returnedResult; 
} 

private async Task<string> GetHolidayDatesAsync(bool sync) { 
    using (var client = new WebClient()) { 
    return sync 
     ? client.DownloadString(SourceURI) 
     : await client.DownloadStringTaskAsync(SourceURI); 
    } 
} 

这种方法避免了重复代码,也避免了与其他“同步过异步”反模式解决方案的常见任何死锁或重入的问题。

请注意,我仍然将结果代码视为正确异步API路径上的“中间步骤”。特别是,内部代码必须回退WebClient(它支持同步和异步),而不是首选的HttpClient(它只支持异步)。一旦所有的调用代码被更改为使用RetrieveHolidayDatesFromSourceAsync而不是RetrieveHolidayDatesFromSource,那么我会重新访问并删除所有技术债务,将其更改为使用HttpClient并且仅为异步。

+1

是'公共任务 RetrieveHolidayDatesFromSourceAsync()'缺少'await'? –

+3

@NickWeaver:不是,它不是'async',所以它不能使用'await'。 –

+0

我明白了。我想知道,因为该方法的名称最后说“异步”。为什么不设置为异步? –

2
public string RetrieveHolidayDatesFromSource() { 
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result; 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
    return returnedResult; 
} 

如果添加。结果的异步调用,它将执行并等待结果的到来,迫使它是同步

UPDATE:

private static string stringTest() 
{ 
    return getStringAsync().Result; 
} 

private static async Task<string> getStringAsync() 
{ 
    return await Task.FromResult<string>("Hello"); 
} 
static void Main(string[] args) 
{ 
    Console.WriteLine(stringTest()); 

} 

解决评论:这工作没有任何问题。

+1

我不认为这是工作,因为我已经在方法中的“result.Result”。拥有this.RetrieveHolidayDatesFromSourceAsync()。结果会将结果更改为一个非任务对象,这将导致“var returnedResult = this.TransformResults(result.Result)”中的错误。 –

+0

我更新了我的答案以显示工作代码。你可以看到getStringAsync是一个在同步方法(stringTest())中调用的异步方法,我得到所需的输出没有错误。你得到的错误是什么? –

+4

“这工作没有任何问题”:)...你可能会更好一点,并解释如何处理在实际使用外部控制台应用程序时产生的死锁。 –