C# 在将旧异步模式包装到TaskCompletionSource中时返回wait?

C# 在将旧异步模式包装到TaskCompletionSource中时返回wait?,c#,async-await,taskcompletionsource,C#,Async Await,Taskcompletionsource,我正在学习C#Asnc等待模式,目前正在阅读S.Cleary的C#Cookbook中的并发 他讨论了使用TaskCompletionSource(TCS)将旧的非TAP异步模式包装到TAP构造中。 我不明白的是,为什么他只是返回TCS对象的Task属性,而不是等待它TCS.Task 下面是示例代码: 要包装的旧方法是DownloadString(…): 公共接口IMyAsyncHttpService { void DownloadString(Uri地址、操作回调); } 将其包装到TAP构造

我正在学习C#Asnc等待模式,目前正在阅读S.Cleary的C#Cookbook中的并发

他讨论了使用TaskCompletionSource(TCS)将旧的非TAP异步模式包装到TAP构造中。 我不明白的是,为什么他只是返回TCS对象的Task属性,而不是等待它TCS.Task

下面是示例代码:

要包装的旧方法是DownloadString(…):

公共接口IMyAsyncHttpService
{
void DownloadString(Uri地址、操作回调);
}
将其包装到TAP构造中:

public static Task<string> DownloadStringAsync(
    this IMyAsyncHttpService httpService, Uri address)
{
    var tcs = new TaskCompletionSource<string>();
    httpService.DownloadString(address, (result, exception) =>
    {
        if (exception != null)
            tcs.TrySetException(exception);
        else
            tcs.TrySetResult(result);
    });
    return tcs.Task;
}
公共静态任务下载StringAsync(
此IMyAsyncHttpService(httpService,Uri地址)
{
var tcs=new TaskCompletionSource();
httpService.DownloadString(地址,(结果,异常)=>
{
if(异常!=null)
tcs.TrySetException(例外);
其他的
tcs.TrySetResult(结果);
});
返回tcs.Task;
}
现在为什么不这样做呢:

public static async Task<string> DownloadStringAsync(
    this IMyAsyncHttpService httpService, Uri address)
{
    var tcs = new TaskCompletionSource<string>();
    httpService.DownloadString(address, (result, exception) =>
    {
        if (exception != null)
            tcs.TrySetException(exception);
        else
            tcs.TrySetResult(result);
    });
    return await tcs.Task;
}
公共静态异步任务下载StringAsync(
此IMyAsyncHttpService(httpService,Uri地址)
{
var tcs=new TaskCompletionSource();
httpService.DownloadString(地址,(结果,异常)=>
{
if(异常!=null)
tcs.TrySetException(例外);
其他的
tcs.TrySetResult(结果);
});
返回等待任务;
}

两者之间有功能上的区别吗?第二个不是更自然吗

您的版本严格来说更为复杂——您不只是返回任务,而是使方法异步,并等待您可以返回的任务。

有一个细微的实际区别(不同于
wait
运行较慢的版本)

在第一个示例中,如果
DownloadString
抛出异常(而不是调用使用
exception
set传递的代理),则该异常将通过调用
DownloadStringAsync
冒泡出现

在第二种情况下,异常被打包到从
DownloadStringAsync
返回的
任务中

因此,假设
DownloadString
抛出此异常(并且没有其他异常发生):

你不会注意到区别

同样,只有当
DownloadString
方法本身抛出时,才会发生这种情况。如果它调用您赋予它的委托,并将
异常
设置为一个值,则这两种情况之间没有明显差异

通过将其标记为async,编译器将生成警告,提示应该考虑等待此方法

您不必将自己的方法标记为
async
,即可获得“任务未等待”警告。以下代码为对
T
U
的调用生成相同的警告:

static async Task Main(string[] args)
{
    Console.WriteLine("Done");
    T();
    U();
    Console.WriteLine("Hello");
}

public static Task T()
{
    return Task.CompletedTask;
}

public static async Task U()
{
    await Task.Yield();
    return;
}
每当您发现自己使用的方法只包含一个
await
,并且这是它最后做的事情(可能返回等待的值除外),您应该问问自己它添加了什么值。除了在异常处理方面的一些差异外,它只是在混合中添加了一个额外的
任务


await
通常是一种表示“我现在没有有用的工作要做,但是当另一个
任务
完成时,我会有工作要做”的方式,这当然不是真的(你以后没有其他工作要做)。因此,跳过“等待”
,只需返回您本应等待的内容即可。

各位,谢谢您的宝贵意见

同时,我阅读了这里引用的资料,并进一步调查了此事:受我的影响,我得出了结论,它的最佳实践是,即使在异步功能链的同步方法中,默认情况下也包括异步等待,并且只有在情况明确表明该方法的行为可能不会与边缘场景中的异步方法的预期行为不同时,才省略异步等待。
例如:同步方法很短,很简单,内部没有可能引发异常的操作。如果同步方法引发异常,调用方将在调用行而不是等待任务的行中获得异常。这显然与预期行为不同


例如,在imo允许省略async Wait的情况下,将调用参数原封不动地移交给下一层

阅读我的答案,为什么返回任务不是一个好主意:


如果不使用wait,基本上会破坏调用堆栈。

哦,读取未插入的代码可能有多困难。这是一项额外的
任务
,不会带来任何改进。您可以只返回一个
任务
,但您可以创建一个状态机,它将只等待此任务,然后返回相同的结果。本文实际上应该回答您的问题:。正如其他人所说,它更复杂。您可以描述一下为什么您认为“就这样做”版本是一种改进。请记住,
await
是处理现有正在运行的
任务的一种方法。它本身不会开始任何新的工作。我的想法是,由于原始方法被转换为TAP构造,代码的用户肯定会等待转换后的方法(因为它是TAP,在某个时候等待运行任务是它应该如何使用的)。通过将其标记为async,编译器将生成警告,提示应该考虑等待此方法。因此,在我看来,从用户的角度来看,这口井的外观和感觉更加轻巧。您仍在返回一个
任务
。如果需要知道作业何时完成,此方法的调用方必须始终(*1)等待
任务。(*1-或其他道德等价物)这是真的,但这是一个有意义的/可测量的绩效惩罚,因此我应该意识到不要直接标记为“没有完成真正的工作”
Task<string> task;
try
{
    task = httpService.DownloadStringAsync(...);
}
catch (Exception e)
{
    // Catches the exception ONLY in your first non-async example
}

try 
{
    await task;
}
catch (Exception e)
{
    // Catches the exception ONLY in your second async example
}
await httpService.DownloadStringAsync(...);
static async Task Main(string[] args)
{
    Console.WriteLine("Done");
    T();
    U();
    Console.WriteLine("Hello");
}

public static Task T()
{
    return Task.CompletedTask;
}

public static async Task U()
{
    await Task.Yield();
    return;
}