我想要在后台任务中执行长时间运行的处理。在任务结束时,我想表明它已经完成。所以基本上我有两个异步任务,我想在后台运行,一个接一个。
我使用continuation执行此操作,但是在完成初始任务之前继续执行。预期的行为是继续只在初始任务完成后运行。
以下是一些演示此问题的示例代码:
// Setup task and continuation
var task = new Task(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
task.Start();
DebugLog函数:
static void DebugLog(string s, params object[] args)
{
string tmp = string.Format(s, args);
System.Diagnostics.Debug.WriteLine("{0}: {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), tmp);
}
预期产出:
TASK Starting
TASK Finishing
CONTINUATION Starting
CONTINUATION Ending
实际输出:
TASK Starting
CONTINUATION Starting
CONTINUATION Ending
TASK Finishing
同样,我的问题是为什么在完成初始任务之前继续开始?如何在第一个任务完成后才能继续运行?
解决方法#1
如果我使初始任务同步,我可以按照预期使上面的代码正常工作 - 就像Wait
上的Task.Delay
一样:
var task = new Task(() =>
{
DebugLog("TASK Starting");
Task.Delay(1000).Wait(); // Wait instead of await
DebugLog("TASK Finishing");
});
由于许多原因,使用Wait
这样很糟糕。一个原因是它阻止了线程,这是我想要避免的。
解决方法#2
如果我创建任务并将其移动到自己的功能中,那么这似乎也有效:
// START task and setup continuation
var task = Test1();
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
static public async Task Test1()
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
}
上述方法的功劳归结为这个有些相关(但不重复)的问题:Use an async callback with Task.ContinueWith
解决方法#2比解决方法#1更好,如果没有解释为什么我的上述初始代码不起作用,我可能采取的方法。
答案 0 :(得分:4)
您的原始代码无效,因为async
lambda正在被转换为下面的async void
方法,该方法没有内置方式来通知任何其他已完成的代码。因此,您所看到的是async void
和async Task
之间的区别。这是您应该避免async void
。
也就是说,如果可以使用您的代码,请使用Task.Run
代替Task
构造函数和Start
;并使用await
而不是ContinueWith
:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
await task;
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
Task.Run
和await
可以更自然地使用async
代码。
答案 1 :(得分:4)
您不应该直接使用Task构造函数来启动任务,尤其是在启动异步任务时。如果要卸载要在后台执行的工作,请使用Task.Run
代替:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
关于延续,最好将它的逻辑附加到lambda表达式的末尾。但是,如果您坚持使用ContinueWith
,则需要使用Unwrap
来获取实际的异步任务并将其存储,以便您可以处理异常:
task = task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
}).Unwrap();
try
{
await task;
}
catch
{
// handle exceptions
}
答案 2 :(得分:0)
将您的代码更改为
// Setup task and continuation
var t1 = new Task(() =>
{
Console.WriteLine("TASK Starting");
Task.Delay(1000).Wait(); // actual work here
Console.WriteLine("TASK Finishing");
});
var t2 = t1.ContinueWith((x) =>
{
Console.WriteLine("C1");
Task.Delay(100).Wait(); // actual work here
Console.WriteLine("C2");
});
t1.Start();
// Exception will be swallow without the line below
await Task.WhenAll(t1, t2);