C# 中的 Task 与 TaskCompletionSource
不要混淆
在 C# 中使用async / await时,您最终会大量使用Task类。除了具有特定用例的void之外,Task是异步方法使用的主要返回类型,以及较少使用的ValueTask。不过,您可能会惊讶地发现,名称相似的TaskCompletionSource不是异步方法可接受的返回类型。TaskCompletionSource 的用途是什么?它如何融入C# 异步编程的图景?
使用 TaskCompletionSource 进行“异步”
简而言之,TaskCompletionSource类用于异步。好吧,诚然,这实际上不是一个真正的词,但您可能直觉地知道我们正在谈论使某些事情异步。
“ Task.Run不也会使事情变得异步吗?”
好问题!虽然Task.Run将同步操作转换为Task(通过在单独的线程上运行),但TaskCompletionSource将已经异步的操作转换为Task。
“如果它已经是异步的,为什么需要将其转变为Task?”
请记住,Task类是启用 C# 的async / await语言支持所必需的,而Task在 .NET 首次发布时并不存在。因此,还有其他(有些人可能会说是“遗留”)方法可以在 C# 中实现异步,这些方法不涉及 Task类,因此与async / await不兼容。例如,有异步编程模型,您可以在其中将AsyncCallback传递给BeginInvoke方法。还有基于事件的异步模式,您可以在其中订阅在完成时引发的事件。这两种都是回调的形式,在操作完成后调用您提供的方法。
无论使用哪种确切模式,您都有可能在 C# 职业生涯的某个阶段遇到回调。当您遇到回调时,您可能会希望有一种方法可以将回调代码转换为可以等待的Task。幸运的是,TaskCompletionSource类可以让您做到这一点!因此,TaskCompletionSource本身不是可等待的,也不是有效的异步方法返回类型。一旦TaskCompletionSource为您提供了Task ,您就可以像在异步方法中执行任何其他操作一样简单地返回该Task。
使用 TaskCompletionSource 替换回调
让我们看一个使用TaskCompletionSource替换回调的具体示例。在上一篇指南中,我们讨论了一个生成图像的应用程序;假设您需要应用程序将这些图像上传到某个地方。假设有一个名为“MyBox”的云文件存储服务,它有一个 .NET 库,上传文件的库方法如下:
public static void UploadFile(string name, byte[] data, Action<bool> onCompleted);
查阅库的文档,您会发现onCompleted是上传完成时调用的回调,如果上传成功,则值为true ,如果上传失败,则值为 false。要使用此库方法上传文件,您必须执行如下操作,假设您的应用程序有可显示文本的名为statusText的内容:
public async void OnUploadButtonClicked()
{
statusText.Text = "Generating Image...";
byte[] imageData = await GenerateImage();
statusText.Text = "Uploading Image...";
MyBox.UploadFile("image.jpg", imageData, success =>
{
statusText.Text = success ? string.Empty : "Error Uploading";
});
}
这很好用,但您确实更愿意使用async / await而不是回调方法,特别是如果您计划在应用程序中大量使用该库。您可以通过创建一个使用TaskCompletionSource的辅助方法来隐藏辅助方法调用者的回调来实现这一点。首先,定义一个TaskCompletionSource实例。它接受一个通用参数,表示您想要返回的类型。在本例中,我们希望返回成功变量的值,它是一个布尔值,因此使用bool作为通用参数。
var taskCompletionSource = new TaskCompletionSource<bool>();
我们将该定义放入辅助类/方法中。
public static class MyBoxHelper
{
public static Task<bool> UploadFile(string name, byte[] data)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
}
}
请注意,我们使用返回类型Task<bool>,期望我们能够使用TaskCompletionSource生成返回布尔值的Task。
现在放大该方法,我们还需要添加对“MyBox”库方法的调用,它将执行上传:
public static Task<bool> UploadFile(string name, byte[] data)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
MyBox.UploadFile(name, data, success => { });
}
接下来,我们需要告诉TaskCompletionSource其操作何时完成,并将操作结果传递给它。TaskCompletionSource 中有一个SetResult方法就是为此目的。毫不奇怪,当我们知道结果时,我们必须在回调中调用它:
public static Task<bool> UploadFile(string name, byte[] data)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
MyBox.UploadFile(name, data, success =>
{
taskCompletionSource.SetResult(success);
});
}
最后,但并非最不重要的一点是,我们需要请求TaskCompletionSource给我们一个Task。这实际上非常简单,因为TaskCompletionSource有一个名为Task的属性!只需直接返回它即可。
public static Task<bool> UploadFile(string name, byte[] data)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
MyBox.UploadFile(name, data, success =>
{
taskCompletionSource.SetResult(success);
});
return taskCompletionSource.Task;
}
为了避免任务永远无法完成,一定要添加异常处理,以便在出现任何问题时任务能够完成。如果出现异常,您可以简单地使用SetCanceled取消(这会抛出一个通用的TaskCanceledException ),但更好的方法是使用SetException提供导致问题的异常。
public static Task<bool> UploadFile(string name, byte[] data)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
try
{
MyBox.UploadFile(name, data, success =>
{
taskCompletionSource.SetResult(success);
});
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
return taskCompletionSource.Task;
}
就是这样!现在,每当MyBox.UploadFile调用回调或抛出异常时,TaskCompletionSource生成的任务都将完成以反映这一点。
重要的是,每个可能的结果都应该由你的逻辑处理;如果有额外的回调指示最终结果(例如 onCanceled、onTimeout),你也需要在每个回调中调用TaskCompletionSource的Set*方法。
一旦你有了辅助方法,你现在可以在代码中的任何位置等待它,而无需使用回调。让我们更新按钮单击事件处理程序:
public async void OnUploadButtonClicked()
{
statusText.Text = "Generating Image...";
byte[] imageData = await GenerateImage();
statusText.Text = "Uploading Image...";
bool success = await MyBoxHelper.UploadFile("image.jpg", imageData);
statusText.Text = success ? string.Empty : "Error Uploading";
}
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~