序列化异步任务和异步延续

时间:2014-09-20 19:42:32

标签: c# async-await task c#-5.0

我想要在后台任务中执行长时间运行的处理。在任务结束时,我想表明它已经完成。所以基本上我有两个异步任务,我想在后台运行,一个接一个。

我使用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更好,如果没有解释为什么我的上述初始代码不起作用,我可能采取的方法。

3 个答案:

答案 0 :(得分:4)

您的原始代码无效,因为async lambda正在被转换为下面的async void方法,该方法没有内置方式来通知任何其他已完成的代码。因此,您所看到的是async voidasync 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.Runawait可以更自然地使用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);