我正在运行以下代码(C#7.1控制台应用程序),但我真的不明白为什么行为有所不同。
如果我等待常规的异步方法调用或Task.Run-它会按预期工作(即应用程序不会立即返回)。但是,如果我使用Task.Factory.StartNew-它会立即返回,而无需实际运行代码。
足够奇怪-如果我使用StartNew,但是在方法内部删除await,它将不会立即返回...
问题:这将立即返回:
static async Task Main(string[] args)
{
await Task.Factory.StartNew(DisplayCurrentInfo);
}
static async Task DisplayCurrentInfo()
{
await WaitAndApologize();
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Thread.Sleep(3000);
}
即-我看不到任何打印到控制台的内容,并且控制台已经关闭。
没问题:不会立即返回:
static async Task Main(string[] args)
{
await DisplayCurrentInfo(); // or await Task.Run(DisplayCurrentInfo);
}
static async Task DisplayCurrentInfo()
{
await WaitAndApologize();
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Thread.Sleep(3000);
}
奇怪:这也不会立即返回:
static async Task Main(string[] args)
{
await Task.Factory.StartNew(DisplayCurrentInfo);
}
static async Task DisplayCurrentInfo()
{
WaitAndApologize();
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Thread.Sleep(3000);
}
WaitAndApologize:
static async Task WaitAndApologize()
{
// Task.Delay is a placeholder for actual work.
await Task.Delay(2000);
// Task.Delay delays the following line by two seconds.
Console.WriteLine("\nSorry for the delay. . . .\n");
}
答案 0 :(得分:7)
如果您使用Task.Factory.StartNew(MethodThatReturnsTask)
,则根据方法是否返回通用任务而返回Task<Task<T>>
或Task<Task>
。
最终结果是您有2个任务:
Task.Factory.StartNew
产生一个调用MethodThatReturnsTask
的任务,我们将此任务称为“任务A” MethodThatReturnsTask
返回一个Task
,我们称其为“任务B”,这意味着将使用处理该问题的StartNew
的重载,最终结果是您取回包裹任务B的任务A。要“正确地”等待这些任务需要2个等待,而不是1个。您的单个等待只是等待任务A,这意味着当它返回时,任务B仍在执行挂起的完成。
要天真地回答您的问题,请等待2次:
await await Task.Factory.StartNew(DisplayCurrentInfo);
但是,令人怀疑的是,为什么需要启动一个任务只是为了启动另一个异步方法。相反,您最好使用第二种语法,在该语法中,您只需等待该方法即可:
await DisplayCurrentInfo();
意见紧随其后:通常,一旦开始编写异步代码,就应在需要产生时保留使用Task.Factory.StartNew
或其任何同级方法。线程(或类似的东西)来调用与其他内容不同步的内容。如果您不需要此特定模式,则最好不要使用它。
答案 1 :(得分:1)
await Task.Factory.StartNew(DisplayCurrentInfo);
第一个示例确实会立即返回,因为您正在创建一个新的Task并等待它,它将调用另一个任务,但是没有等待。考虑如下所示的伪代码,以保持等待状态。
await Task.Factory.StartNew( await DisplayCurrentInfo); //Imaginary code to understand
在第三个示例中,WaitAndApologize
未被等待,但是它被线程睡眠阻塞(全线程被阻塞)。
仅靠代码的帮助,我们不能说Task factory创建了一个新线程或仅在现有线程上运行。
如果它在同一线程中运行,则整个线程将被阻塞,因此您会感觉到代码正在await Task.Factory.StartNew(DisplayCurrentInfo);
处等待,但通常不会发生。
如果它在其他网络线程上运行,您将不会获得与上述相同的结果。
答案 2 :(得分:0)
致电Task.Factory.StartNew(DisplayCurrentInfo);
时使用的是签名:
public Task<TResult> StartNew<TResult>(Func<TResult> function)
现在,由于您要传递带有签名Task DisplayCurrentInfo()
的方法,因此TResult
的类型为Task
。
换句话说,您正在从Task<Task>
返回一个Task.Factory.StartNew(DisplayCurrentInfo)
,然后等待返回Task
的外部任务-而您并没有等待它。因此,它立即完成。