我正在实现函数的异步和普通版本。我已经实现了异步版本。对于普通版本,我可以使用正常的对应部分复制粘贴和替换异步函数调用的使用。或者我可以使用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
}
第二种方法可能存在问题吗?
答案 0 :(得分:2)
性能将受到影响。多少是不可能的 - 你是代码中的那个,你可以为你的确切用例进行分析。
但是,制作仅调用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
,您将失去许多异常处理功能。例如,异常堆栈跟踪将全部搞砸,您将需要处理由创建任务的方法和Result
调用本身抛出的异常 - 第一个将异常抛出到实际上必须等待的第一个await
的点,以及所有延续的第二个点。你永远不知道哪个是哪个。
答案 1 :(得分:0)
首先,如果您有自然异步操作,则应该公开异步API。正如Damien指出的那样,"Should I expose synchronous wrappers for my asynchronous methods?"的答案是&#34; No&#34;。
这些包装器的一个问题是 没有模式适用于所有场景!我在文章中描述了各种同步异步黑客攻击在Brownfield Async。他们每个人都有缺点;正如Luaan指出的那样,阻塞黑客有可能陷入僵局。
但是,如果您有充分的理由这样做(即,您的库历史上有同步方法,并且您正在添加异步方法但希望保持同步方法以实现向后兼容性,至少对于版本或两个),然后你可以使用&#34;布尔标志hack&#34;正如我的文章所述。
斯蒂芬·图布不久前向我展示了这个伎俩。这个想法是(私有)实现函数采用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);
}