使用带结果的异步版本实现函数

问题描述:

我正在实现函数的异步和正常版本。我已经实现了异步版本。对于正常版本,我可以复制粘贴并用其正常计数器部件替换异步函数调用的使用。或者我可以使用Result调用异步版本,如下所示。使用带结果的异步版本实现函数

第一种方法:

public int SomeTask(int param) 
{ 
    //Something going on 
    return SomeOtherTask(); 
} 

public async Task<int> SomeTaskAsync(int param) 
{ 
    //Something going on (copy pasted) 
    return await SomeOtherTaskAsync();  
} 

第二种方法:

public int SomeTask(int param) 
{ 
    return SomeTaskAsync(param).Result; 
} 

public async Task<int> SomeTaskAsync(int param) 
{ 
    //some function calls with await 
} 

是否与第二种方法可能出现的问题?

+3

对你可能想读 - [?我应该公开的同步方法异步包装器(http://blogs.msdn.com/b/pfxteam/ archive/2012/03/24/10287244.aspx)和[我应该公开异步方法的同步包装?](http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx )。两篇文章的答案通常都不是。 –

性能将受到影响。有多少是不可能告诉的 - 你是代码的人,你可以为你的确切用例做分析。

但是,制作只调用Result的方法的同步版本毫无意义 - 您的方法的用户可以执行同样的操作。问题是,无论如何,这可能是相当危险的,特别是当涉及同步上下文时。考虑示例代码:

async void btnTest_Click(object sender, EventArgs e) 
{ 
    await DoSomethingAsync(); 
} 

async Task DoSomethingAsync() 
{ 
    await Task.Delay(1000); 
} 

这工作正常。现在,让我们来试试你的“同步”的版本:

async void btnTest_Click(object sender, EventArgs e) 
{ 
    DoSomethingAsync().Result; 
} 

async Task DoSomethingAsync() 
{ 
    await Task.Delay(1000); 
} 

哎呀,你有一个僵局。 UI线程正在等待DoSomethingAsync完成,但DoSomethingAsync需要在UI线程上完成执行。如果您同步等待,则永远不会假定async方法将运行。

此外,通过使用Result而不是await,您将失去很多异常处理功能。例如,异常堆栈跟踪将全部搞乱,您需要处理由创建任务的方法和调用本身的方法抛出的异常 - 第一个将抛出异常直至第一个点实际上必须等待,并且所有延续的第二个。你永远不知道哪个是哪个。

首先,如果你有一个自然异步操作,你应该公开一个异步API。正如达米恩指出的那样,"Should I expose synchronous wrappers for my asynchronous methods?"的答案是“否”。

这些类型的包装的一个问题是,没有模式适用于所有情景!我在我的文章Brownfield Async上描述了各种异步同步攻击。他们每个人都有缺点;正如Luaan指出的那样,阻止黑客有可能发生死锁。但是,如果你有这样一个真正的理由(即你的库历史上有同步方法,并且你正在添加异步方法,但想保持同步方法的向后兼容性,至少对于一个版本或者两个),那么你可以使用我的文章中描述的“布尔标志黑客”。

Stephen Toub前段时间向我展示了这个技巧。这个想法是(私有)实现函数采用参数bool sync,如果是true,那么它返回的任务保证已经完成。这避免了经常堵塞僵局问题:文章

private async Task<int> SomeTaskAsync(int param, bool sync) 
{ 
    // Every `await` in your code needs to honor `sync` 
    if (sync) 
    return SomeOtherTask(); 
    else 
    return await SomeOtherTaskAsync(); 
} 

public int SomeTask(int param) 
{ 
    return SomeTaskAsync(param, sync: true).GetAwaiter().GetResult(); 
} 

public Task<int> SomeTaskAsync(int param) 
{ 
    return SomeTaskAsync(param, sync: false); 
}