C# 如何从非异步方法调用异步方法?

C# 如何从非异步方法调用异步方法?,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我有以下方法: public string RetrieveHolidayDatesFromSource() { var result = this.RetrieveHolidayDatesFromSourceAsync(); /** Do stuff **/ var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/

我有以下方法:

    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;
        }
    }
我错过什么了吗

注意:出于某种原因,当我中断上述异步方法时,当它到达行
var json=await httpClient.GetStringAsync(SourceURI)
时,它只是超出了断点,我无法返回该方法

public string RetrieveHolidayDatesFromSource() {
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result;
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
    return returnedResult;
}
如果将.Result添加到异步调用中,它将执行并等待结果到达,从而强制其同步

更新:

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());

}
private静态字符串stringTest()
{
返回getStringAsync().Result;
}
私有静态异步任务getStringAsync()
{
返回等待任务。FromResult(“Hello”);
}
静态void Main(字符串[]参数)
{
Console.WriteLine(stringTest());
}

针对评论:这项工作没有任何问题

我错过什么了吗

对。异步代码本质上意味着在操作进行时不使用当前线程。同步代码——就其性质而言——意味着当前线程在操作进行时被阻塞。这就是为什么从同步代码中调用异步代码毫无意义。事实上,正如我在博客上描述的那样

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

public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result); /** Where result gets used **/
    return returnedResult;
}
公共异步任务检索HolidayDatesFromSourceAsync(){
var result=wait this.DoRetrieveHolidayDatesFromSourceAsync();
/**做事**/
var returnedResult=this.TransformResults(result);/**使用result的位置**/
返回返回结果;
}
正如我在我的中所描述的,您应该“一路异步”。如果您不这样做,您将不会从异步中获得任何好处,那么为什么还要麻烦呢

但假设你最终对异步感兴趣,但现在你不能改变一切,你只想改变你应用的一部分。这是很常见的情况

在这种情况下,正确的方法是公开同步和异步API。最终,在升级所有其他代码之后,可以删除同步API。在我的文章中,我探索了这类场景的各种选择;我个人最喜欢的是“bool参数hack”,它看起来像这样:

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);
  }
}
公共字符串检索HolidayDatesFromSource(){
返回此.DoRetrieveHolidayDatesFromSourceAsync(sync:true).GetAwaiter().GetResult();
}
公共任务检索HolidayDatesFromSourceAsync(){
返回此.DoRetrieveHolidayDatesFromSourceAsync(同步:false);
}
专用异步任务DoRetrieveHolidayDatesFromSourceAsync(布尔同步){
var result=wait this.GetHolidayDatesAsync(同步);
/**做事**/
var returnedResult=this.TransformResults(结果);
返回返回结果;
}
专用异步任务GetHolidayDatesAsync(bool同步){
使用(var client=new WebClient()){
返回同步
?客户端下载字符串(SourceURI)
:wait client.downloadstringtasksync(SourceURI);
}
}
这种方法避免了代码重复,也避免了其他“异步同步”反模式解决方案常见的死锁或重入问题


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

我认为这不起作用,因为我已经在方法中有了“result.result”。使用此.RetrieveHolidayDatesFromSourceAsync().Result将结果更改为非任务对象,这将导致“var returnedResult=this.TransformResults(Result.Result)”中出现错误。我更新了答案以向您展示工作代码。您可以看到getStringAsync是在同步方法(stringTest())中调用的异步方法,我获得了所需的输出,没有错误。您得到的错误是什么?“此操作没有任何问题”:。。。在控制台应用程序之外实际使用时,您可能会更好地解释如何处理由此产生的死锁。如果您在UI环境或任何具有单线程的环境中运行,则可能会有死锁。在这种情况下,对
.Result
的调用将阻止进一步的执行,但是
async
方法中的
wait
可能会在被阻止线程的运行时注册它的继续。混合使用异步和同步等待总是非常危险的,尤其是在UI环境中。它“工作时没有任何问题”这一事实有点侥幸,完全是因为在等待时返回的任务已经完成。任何使用
HttpClient
WebClient
的代码都不会出现这种情况。它是
public Task retrieveholidaysfromsourceasync()
缺少
wait
?@NickWeaver:不。它不是
async
,因此不能使用
wait
。我明白了。我想知道,因为该方法的名称在末尾声明为“Async”。为什么不设置为async?@NickWeaver:因为它是一个简单的方法,所以它直接返回任务。它仍然是一个异步API,因此使用了
Async
后缀,但它的实现没有使用
Async
/
wait
。感谢您的解释。
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);
  }
}