1

我正在研究 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;
}

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

4

5 回答 5

1

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

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

await通常是一种表示“我现在没有有用的工作要做,但是当其他工作Task完成时会有”的一种方式,这当然不是真的(你以后没有其他工作要做)。因此,请跳过await并返回您所等待的内容。

于 2019-02-18T11:40:16.050 回答
0

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

于 2019-02-18T11:03:11.133 回答
0

有一个细微的实际差异(除了await运行速度较慢的版本)。

在第一个示例中,如果DownloadString抛出异常(而不是调用您使用exceptionset 传递的委托),那么该异常将通过您对DownloadStringAsync.

第二种,将异常打包到Task返回的 from 中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设置为一个值,那么您的两种情况之间没有明显的区别。

于 2019-02-18T11:08:24.900 回答
0

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

与此同时,我已经阅读了此处引用的来源并进一步调查了此事:受https://blog.stephencleary.com/2016/12/eliding-async-await.html的影响我得出的结论是,这是最好的实践中即使在异步函数链的同步方法中也默认包含 async-await,并且仅当情况清楚地表明该方法不会像预期的异步方法和边缘场景那样表现不同时才省略异步等待。
如:同步方法短小简单,内部没有任何可能抛出异常的操作。如果同步方法抛出异常,调用者将在调用行而不是等待任务的行中获得异常。这显然是对预期行为的改变。

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

于 2019-02-19T13:36:02.697 回答
0

阅读我的答案,为什么返回任务不是一个好主意:C# 中“返回等待”的目的是什么?

如果你不使用等待,基本上你会破坏你的调用堆栈。

于 2019-02-19T18:50:44.847 回答