在任务中等待异步/等待的问题

57
我在我的main()函数中有这个结构,它创建了
var tasks = new List<Task>();

var t = Task.Factory.StartNew(
    async () =>
    {
        Foo.Fim();
        await Foo.DoBar();
    });

//DoBar not completed
t.Wait();
//Foo.Fim() done, Foo.DoBar should be but isn't

然而,当我使用.Wait等待t时,它不会等待DoBar()的调用完成。 我该如何让它真正等待?


3
请展示一个短小但是完整的程序来阐明问题。 - Jon Skeet
1
Stephen Taub写了一篇很棒的文章,介绍如何启动任务。该文章实际上有一个示例,看起来与您的示例完全相同(即Task.StartNew(async() =>...)),并解释了正在进行的操作。 - default
3个回答

114

不建议在使用async-await时使用Task.Factory.StartNew,应该使用Task.Run代替:

var t = Task.Run(
    async () =>
    {
        Foo.Fim();
        await Foo.DoBar();
    });
Task.Factory.StartNew API在 基于任务的异步模式(TAP)async-await 之前就已经存在了。因为你使用一个可能是异步的lambda表达式来启动任务,所以它将返回一个 Task<Task> 对象。使用 Unwrap 方法可以提取内部任务,但是使用 Task.Run 方法会隐式地为你执行这个操作。
如果想要进行更深入的比较,可以参考 Stephen Toub 的相关文章:Task.Run vs Task.Factory.StartNew

1
有趣的是,Task.Run 的差异很难察觉。这也解答了我对于为什么 Task.Run 与 async/await 不同的困惑。谢谢。 - Nathan Cooper
@NathanCooper 这就是在没有破坏性更改的情况下进行逐步添加的库中会发生的事情。 - i3arnon
1
是的,我的理解是它们都是相同的,只是默认值不同。正如文章所说,等价于"Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);"。但是当你使用async/await而不是简单的actions时,这显然需要进一步阅读。 - Nathan Cooper
1
今天我为了找出为什么Factory.Start返回Task<Task...而疯狂,尽管我的所有代码看起来都很正常。谢谢! - tylerjgarland
2
我本来正想问如何使用新的工厂传递LongRunning参数,然后发现了你写的这篇优秀的博客文章:http://blog.i3arnon.com/2015/07/02/task-run-long-running/你的回答和那篇文章结合起来真的帮了我很多! - istrupin

8

看起来通过对任务进行Unwrap()操作,我可以获得期望的功能。 我不太确定这背后的原因,但我认为它有效。

var t = Task.Factory.StartNew(
            async () =>
                {
                        Foo.Fim();
                        await Foo.DoBar();
                }).Unwrap();

编辑:我已经查找了Unwrap()的描述: 创建代表Task<Task<T>>异步操作的代理任务 我认为这通常是任务所做的事情,但如果我需要调用unwrap,那我想那也没关系。


是的,另一个答案已经非常全面了。Task.Run() 似乎是正确的选择。 - Nathan Cooper
你确定你需要在任务内部使用 Foo.Dim() 吗?也就是说,也许你可以简单地这样做:Foo.Fimm(); Foo.DoBar().Wait(); - 而不需要通过 RunStartNew 创建一个新的 Task - noseratio - open to work
1
@Noseratio 说得好。我没有提供上下文来说明为什么需要它(实际上是需要的),但检查我们是否过度工程化总是很好的。 - Nathan Cooper
Unwrap() 最终会抛出一个 Start may not be called on a promise-style task. 异常:/ - Douglas Gaskell

0

我最近遇到了类似的问题,发现你只需要让DoBar()返回一些值,并使用.Result而不是等待即可解决。

var g = Task.Run(() => func(arg));

var val = g.Result;

这将会等待func返回其输出并将其赋值给val。


-1 是因为 OP 并不想返回一个值,OP 的问题是关于 async/await 的,而这里纯粹是同步的,并且似乎是为了调用 Result 而返回一个可抛弃的值。如果你想以异步方式执行上述操作,可以使用 "var val = await g;" 来等待任务的结果。如果你不关心结果,那么你可以简单地在任务上调用 .Wait()--无需从函数中返回一个可抛弃的值并使用 Result 等待。 - Josh
2
他的问题是:“我该如何让它真正等待?”他说过什么关于返回值的事情吗? - Siddharth Shakya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接