我是否需要保留对“Task.Run”方法返回的“任务”的引用?
Do I need to keep a reference to the `Task` returned by `Task.Run` method?
如果我有异步方法:
async Task Process()
{
while (condition)
{
await ...;
}
}
而且我需要安排这个方法的执行。所以我使用:
Task.Run(Process);
我是否需要保留 Task.Run
方法返回的 Task
对象的引用以确保 Process
执行到完成?
这里有更多的上下文:我需要在我的应用程序中创建很多(大约 50K)任务队列。所以我想创建一个无线程队列处理设计,当队列为空时(除了内存之外)没有成本(因为大多数这些队列都是空的)。
示例要点 class:https://gist.github.com/hemant-jangid/e990b27507596c086e5651f504d0521f
一般来说,您不应该扔掉 Task
。这不是关于确保任务会运行(它会1),而是关于观察异常。如果您不对 Task
执行 something,那么找出问题所在的唯一方法是添加一个 UnobservedTaskException
事件处理程序。不理想。
最好将 await
代码放在 try
/catch
块中,或者注册一个 ContinueWith
处理程序,在您指定的地方应该调用错误.
不幸的是(IMO).NET 的行为在 4.5 左右发生了变化,因此未观察到的异常不再破坏进程。这意味着,如果您未能观察到它们,那么您的代码就会失败,并且 没有办法 tracing/logging 这种情况。
在 general 中,围绕一个已经产生 Task
的方法做 Task.Run
是不必要的,而且会适得其反。该方法已经承诺提供 Task
- 为什么将其包装在第二个中? (该方法如何以及为何 returns Task
是该方法的实现细节,none 是调用者业务的实现细节)。只有当你调用的方法确实 significant CPU-bound 工作并且你(调用者)当前处于 "precious" 上下文时,你才应该真正这样做- 例如在 UI 线程上。
1假设整个过程存活时间足够长,等等
为此,我们将不得不更深入地研究任务。 Tasks本质上是线程,Task的起源是为线程池调度的。现在考虑这句话:
Tasks are essentially threads; but they are background threads.
Background threads are automatically aborted when all foreground
threads finish. So, if you don't do anything with the task and the
program ends, there's a chance the task won't complete.
所以你可能会想,如果后台线程在所有前台线程完成时自动中止,则必须保留对 task
的引用,事实上,这就是为什么你应该始终如前所述等待任务在 this post answer.
但是 让我们深入研究 Thread documentation 并查看这一行:
It is not necessary to retain a reference to a Thread object once you
have started the thread. The thread continues to execute until the
thread procedure is complete.
这意味着,虽然上面提到后台线程会在所有前台线程完成时自动中止,但无需保留对它的引用。
事实上,根据文档,创建任务的正确方法是使用它的工厂:
You can also use the StartNew method to create and start a task in one
operation. This is the preferred way to create and start tasks if
creation and scheduling do not have to be separated
如果我有异步方法:
async Task Process()
{
while (condition)
{
await ...;
}
}
而且我需要安排这个方法的执行。所以我使用:
Task.Run(Process);
我是否需要保留 Task.Run
方法返回的 Task
对象的引用以确保 Process
执行到完成?
这里有更多的上下文:我需要在我的应用程序中创建很多(大约 50K)任务队列。所以我想创建一个无线程队列处理设计,当队列为空时(除了内存之外)没有成本(因为大多数这些队列都是空的)。
示例要点 class:https://gist.github.com/hemant-jangid/e990b27507596c086e5651f504d0521f
一般来说,您不应该扔掉 Task
。这不是关于确保任务会运行(它会1),而是关于观察异常。如果您不对 Task
执行 something,那么找出问题所在的唯一方法是添加一个 UnobservedTaskException
事件处理程序。不理想。
最好将 await
代码放在 try
/catch
块中,或者注册一个 ContinueWith
处理程序,在您指定的地方应该调用错误.
不幸的是(IMO).NET 的行为在 4.5 左右发生了变化,因此未观察到的异常不再破坏进程。这意味着,如果您未能观察到它们,那么您的代码就会失败,并且 没有办法 tracing/logging 这种情况。
在 general 中,围绕一个已经产生 Task
的方法做 Task.Run
是不必要的,而且会适得其反。该方法已经承诺提供 Task
- 为什么将其包装在第二个中? (该方法如何以及为何 returns Task
是该方法的实现细节,none 是调用者业务的实现细节)。只有当你调用的方法确实 significant CPU-bound 工作并且你(调用者)当前处于 "precious" 上下文时,你才应该真正这样做- 例如在 UI 线程上。
1假设整个过程存活时间足够长,等等
为此,我们将不得不更深入地研究任务。 Tasks本质上是线程,Task的起源是为线程池调度的。现在考虑这句话:
Tasks are essentially threads; but they are background threads. Background threads are automatically aborted when all foreground threads finish. So, if you don't do anything with the task and the program ends, there's a chance the task won't complete.
所以你可能会想,如果后台线程在所有前台线程完成时自动中止,则必须保留对 task
的引用,事实上,这就是为什么你应该始终如前所述等待任务在 this post answer.
但是 让我们深入研究 Thread documentation 并查看这一行:
It is not necessary to retain a reference to a Thread object once you have started the thread. The thread continues to execute until the thread procedure is complete.
这意味着,虽然上面提到后台线程会在所有前台线程完成时自动中止,但无需保留对它的引用。
事实上,根据文档,创建任务的正确方法是使用它的工厂:
You can also use the StartNew method to create and start a task in one operation. This is the preferred way to create and start tasks if creation and scheduling do not have to be separated