如何声明将等待另一个任务的未启动任务?
How to declare a not started Task that will Await for another Task?
我已经完成了这个单元测试,但我不明白为什么 "await Task.Delay()" 不等待!
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
这是单元测试输出:
这怎么可能 "await" 不等待,主线程继续?
new Task(async () =>
一个任务不需要Func<Task>
,而是Action
。它将调用您的异步方法并期望它在 returns 时结束。但事实并非如此。它returns一个任务。新任务不等待该任务。对于新任务,一旦方法返回,作业就完成了。
您需要使用已经存在的任务,而不是将其包装在新任务中:
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Func<Task> asyncMethod = async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
};
Console.WriteLine("Main.BeforeStart");
Task myTask = asyncMethod();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
[Fact]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(() =>
{
Console.WriteLine("Task.BeforeDelay");
Task.Delay(3000).Wait();
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.True(isOK, "OK");
}
问题是您使用的是非泛型 Task
class,这并不意味着会产生结果。因此,当您创建传递异步委托的 Task
实例时:
Task myTask = new Task(async () =>
...代表被视为 async void
。 async void
不是 Task
,它不能被等待,它的异常不能被处理,它是 Whosebug 和其他地方沮丧的程序员制作的 thousands of questions 的来源。解决办法是使用通用的Task<TResult>
class,因为你想return一个结果,而这个结果是另一个Task
。所以你必须创建一个 Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
现在,当您 Start
外部 Task<Task>
时,它将几乎立即完成,因为它的工作只是创建内部 Task
。然后你还必须等待内部 Task
。这是如何完成的:
myTask.Start(TaskScheduler.Default);
Task myInnerTask = await myTask;
await myInnerTask;
你有两个选择。如果您不需要对内部 Task
的显式引用,那么您可以等待外部 Task<Task>
两次:
await await myTask;
...或者您可以使用内置的扩展方法Unwrap
,将外部任务和内部任务合二为一:
await myTask.Unwrap();
当您使用更流行的创建热任务的 Task.Run
方法时,这种展开会自动发生,因此 Unwrap
现在不常使用。
如果您决定您的异步委托必须 return 一个结果,例如 string
,那么您应该将 myTask
变量声明为 Task<Task<string>>
类型].
注意:我不赞成使用Task
构造函数来创建冷任务。由于我不太清楚的原因,这种做法通常是不受欢迎的,但可能是因为它很少被使用,以至于它有可能让其他不知道 users/maintainers/reviewers 代码的人措手不及。
一般建议:每次提供异步委托作为方法的参数时都要小心。理想情况下,此方法应该期望一个 Func<Task>
参数(意味着理解异步委托),或者至少一个 Func<T>
参数(意味着至少生成的 Task
不会被忽略)。不幸的是,此方法接受 Action
,您的委托将被视为 async void
。这很少是你想要的,如果有的话。
我已经完成了这个单元测试,但我不明白为什么 "await Task.Delay()" 不等待!
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
这是单元测试输出:
这怎么可能 "await" 不等待,主线程继续?
new Task(async () =>
一个任务不需要Func<Task>
,而是Action
。它将调用您的异步方法并期望它在 returns 时结束。但事实并非如此。它returns一个任务。新任务不等待该任务。对于新任务,一旦方法返回,作业就完成了。
您需要使用已经存在的任务,而不是将其包装在新任务中:
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Func<Task> asyncMethod = async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
};
Console.WriteLine("Main.BeforeStart");
Task myTask = asyncMethod();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
[Fact]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(() =>
{
Console.WriteLine("Task.BeforeDelay");
Task.Delay(3000).Wait();
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.True(isOK, "OK");
}
问题是您使用的是非泛型 Task
class,这并不意味着会产生结果。因此,当您创建传递异步委托的 Task
实例时:
Task myTask = new Task(async () =>
...代表被视为 async void
。 async void
不是 Task
,它不能被等待,它的异常不能被处理,它是 Whosebug 和其他地方沮丧的程序员制作的 thousands of questions 的来源。解决办法是使用通用的Task<TResult>
class,因为你想return一个结果,而这个结果是另一个Task
。所以你必须创建一个 Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
现在,当您 Start
外部 Task<Task>
时,它将几乎立即完成,因为它的工作只是创建内部 Task
。然后你还必须等待内部 Task
。这是如何完成的:
myTask.Start(TaskScheduler.Default);
Task myInnerTask = await myTask;
await myInnerTask;
你有两个选择。如果您不需要对内部 Task
的显式引用,那么您可以等待外部 Task<Task>
两次:
await await myTask;
...或者您可以使用内置的扩展方法Unwrap
,将外部任务和内部任务合二为一:
await myTask.Unwrap();
当您使用更流行的创建热任务的 Task.Run
方法时,这种展开会自动发生,因此 Unwrap
现在不常使用。
如果您决定您的异步委托必须 return 一个结果,例如 string
,那么您应该将 myTask
变量声明为 Task<Task<string>>
类型].
注意:我不赞成使用Task
构造函数来创建冷任务。由于我不太清楚的原因,这种做法通常是不受欢迎的,但可能是因为它很少被使用,以至于它有可能让其他不知道 users/maintainers/reviewers 代码的人措手不及。
一般建议:每次提供异步委托作为方法的参数时都要小心。理想情况下,此方法应该期望一个 Func<Task>
参数(意味着理解异步委托),或者至少一个 Func<T>
参数(意味着至少生成的 Task
不会被忽略)。不幸的是,此方法接受 Action
,您的委托将被视为 async void
。这很少是你想要的,如果有的话。