将旧的异步模式包装到TaskCompletionSource中时,返回等待状态?

时间:2019-02-18 10:48:16

标签: c# async-await taskcompletionsource

我正在研究C#Asnc-await模式,目前正在阅读S. Cleary在C#Cookbook中的并发性

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

这是示例代码:

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

public interface IMyAsyncHttpService
{
    void DownloadString(Uri address, Action<string, Exception> callback);
}

将其包装到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;
}

现在为什么不这样做呢?

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;
}

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

5 个答案:

答案 0 :(得分:1)

  

通过将其标记为异步,编译器将生成警告,应考虑等待该方法

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

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并且它做的最后一件事(可能返回等待的值)时,您应该问自己添加的是什么值。除了在异常处理方面存在一些差异之外,它只是在混合中添加了额外的Task

await通常是一种指示“我目前没有任何有用的工作要做,但是当其他Task完成时会有”的一种方式当然是不正确的(您可以(以后没有其他工作可做)。因此,跳过await,只返回您本来要等待的内容即可。

答案 1 :(得分:0)

您的版本严格更加复杂-您使方法异步,而不仅仅是返回任务,然后等待您可能要返回的任务。

答案 2 :(得分:0)

在实用上有一个细微的差别(除了await运行较慢的版本以外)。

在第一个示例中,如果DownloadString抛出异常(而不是调用委托,而是设置exception来传递该委托),那么该异常将在您对DownloadStringAsync的调用中冒泡。

第二,将异常打包到Task返回的DownloadStringAsync中。

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

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

您不会注意到其中的区别。

同样,仅当DownloadString方法本身抛出时,才会发生这种情况。如果改为调用您将exception设置为值的委托,那么这两种情况之间就没有明显的区别。

答案 3 :(得分:0)

伙计,感谢您的有用评论。

同时,我阅读了这里引用的资源并进一步研究了此问题:受https://blog.stephencleary.com/2016/12/eliding-async-await.html的影响,我得出的结论是,即使在同步方法中,默认情况下,它的最佳做法还是默认包括async-await异步功能链,并且仅在情况明确表明该方法的行为不会像边缘方案中的异步方法所预期的那样时,才忽略异步等待。
例如:同步方法简短,简单,并且内部没有操作可能引发异常。如果同步方法引发异常,则调用者将在调用行而不是等待Task的行中获得异常。显然,这与预期行为有所不同。

例如,在imo允许省略async-await的情况下,将调用参数不变地移交给下一层。

答案 4 :(得分:0)

请阅读我的答案,为什么返回任务不是一个好主意:What is the purpose of "return await" in C#?

如果您不使用等待状态,基本上可以中断呼叫堆栈。