根据我阅读的Microsoft TPL文档(link),调用Task.Wait()
方法将阻塞当前线程,直到该任务完成(或取消或出现错误)。但是它还说,如果所讨论的任务尚未开始,则Wait
方法将通过要求调度程序重新分配它来尝试在其自己的线程上运行 ,从而减少了阻塞造成的浪费量。
我有一个系统,其中的任务(一旦运行)通过启动其他任务并等待其结果来收集数据而开始。反过来,这些其他任务首先是从其他任务收集数据而来,可能要几百层。我真的不想阻止许多任务,并等待最后一个任务最终完成。
但是,当我在测试控制台应用程序中尝试此操作时,Task.Wait()
似乎根本没有启动任何东西。
构建一系列必须全部以最小的浪费周期相互等待的任务的正确方法是什么?有点像ContinueWith,只是从系列中的最后一个任务开始...
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var source = new CancellationTokenSource();
var token = source.Token;
// Create a non-running task.
var task = new Task<string[]>(() => InternalCompute(token), token);
// Isn't this supposed to start the task?
task.Wait(CancellationToken.None);
// I realise this code now won't run until the task finishes,
// it's here for when I use task.Start() instead of task.Wait().
Console.WriteLine("Press any key to cancel the process.");
Console.ReadKey(true);
source.Cancel();
Console.WriteLine("Source cancelled...");
Console.WriteLine("Press any key to quit.");
Console.ReadKey(true);
}
private static string[] InternalCompute(CancellationToken token)
{
string[] data;
try
{
data = Compute(token);
}
catch (TaskCanceledException ex)
{
return null;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return new[] { ex.Message };
}
Console.WriteLine("Post-processor starting.");
for (int i = 0; i < data.Length; i++)
if (data[i] is null)
Console.WriteLine($"Null data at {i}.");
else
Console.WriteLine($"Valid data at {i}.");
Console.WriteLine("Post-processor completed.");
return data;
}
/// <summary>
/// This method stands in for an abstract one to be implemented by plug-in developers.
/// </summary>
private static string[] Compute(CancellationToken token)
{
var data = new string[10];
for (int i = 0; i < data.Length; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(250);
data[i] = i.ToString();
Console.WriteLine($"Computing item {i + 1}...");
}
return data;
}
}
}
答案 0 :(得分:3)
Task
通常分为两组-“冷”任务和“热”任务。 “冷”任务是尚未启动且尚未运行的任务。 “热门”任务是当前可能正在运行或可能不在运行的任务,但重要的是,如果尚未运行,则它们可以随时运行。它们仅需即可运行,但尚未为其分配所需的资源(线程)。
this post所说的是执行“热”任务,否则该任务没有机会运行。 “热”任务是通过调用Task.Run()
。他们也是您将从异步方法中收到的Task
的类型。另一方面,new Task(...)
为您提供“冷”任务。除非或直到您在该任务上调用Start
或道德上等效的方法,否则它将保持“冷”状态。它正在显式调用这些方法之一,从而使其变得“热”而不是“冷”。
通常,您不想一直都在使用“冷”任务,这就是为什么直接调用Task
构造函数的原因。从制定时间表应该如何工作之前,这确实是一个糟糕的实验。大多数现代代码根本不希望使用“冷”任务。
以上文章的关键语录是这样的:
但是,如果尚未开始执行,则Wait可以将目标任务从排队的调度程序中拉出,并在当前线程上内联执行。
如果您尚未在任务上调用Start
,则说明该任务尚未与调度程序一起排队-显然,我们无法执行上面的操作。
答案 1 :(得分:0)
这是the article中引起混乱的部分(添加了重点)。
如果正在等待的任务已经开始执行,则必须等待。但是,如果尚未开始执行,则Wait可以将目标任务从排队的调度程序中拉出,并在当前线程上内联执行。
这是Task.Start
方法的描述:
启动
Task
,安排其执行到当前的TaskScheduler
。
这些是任务生命周期的eight different stages:
public enum TaskStatus
{
Created = 0, // The task has been initialized but has not yet been scheduled.
WaitingForActivation = 1, // The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure.
WaitingToRun = 2, // The task has been scheduled for execution but has not yet begun executing.
Running = 3, // The task is running but has not yet completed.
WaitingForChildrenToComplete = 4, // The task has finished executing and is implicitly waiting for attached child tasks to complete.
RanToCompletion = 5, // The task completed execution successfully.
Canceled = 6, // The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken while the token was in signaled state, or the task's CancellationToken was already signaled before the task started executing.
Faulted = 7 // The task completed due to an unhandled exception.
}
本文隐含地讨论了处于WaitingForActivation
或WaitingToRun
阶段的热门任务,并说明了在哪些条件下可以在内部进行这些任务调用其Running
方法的Wait
阶段。它不是在谈论Created
阶段的冷任务。如果没有调用其Start
或RunSynchronously
方法,则无法完成此阶段的任务。换句话说,.NET基础结构永远不会自动将任务的温度从冷更改为热。此职责100%属于使用Task
constructor创建任务的应用程序代码。
作为旁注,值得在备注中引用此句子:
此构造函数仅应在要求将任务的创建和启动分开的高级方案中使用。