我有一个异步方法,该方法调用了另一个异步方法,但是,我希望它在单独的线程上并行运行:
public async Task<Page> ServePage() {
Task.Run(() => DoThings(10)); // warning here
// ... other code
return new Page();
}
public async Task DoThings(int foo) {
// stuff
}
警告指出:
由于不等待此调用,因此在调用完成之前将继续执行当前方法。考虑将'await'运算符应用于调用结果。
实际上,这就是我想要做的。为什么会收到编译器警告? Task.Run的语法不正确吗?
答案 0 :(得分:3)
TL; DR
收到警告的原因是
Task.Run(() => DoThings(10)); // warning here
返回一个任务,并且由于您的ServePage
方法被标记为异步,因此编译器认为您应该等待Task
详细信息
您正在混合两种截然不同的范例,巧合的是它们都涉及Task
,即:
Task.Run()
,通常通过利用多个可用内核来并行化CPU绑定工作async / await
,对于在不阻塞(浪费)线程的情况下等待I / O绑定操作完成很有用。例如,如果您想同时执行3个CPU绑定操作,并且由于Task.Run
返回一个Task
,那么您可以做的是:
public Page ServePage() // If we are CPU bound, there's no point decorating this as async
{
var taskX = Task.Run(() => CalculateMeaningOfLife()); // Start taskX
var taskY = Task.Run(() => CalculateJonSkeetsIQ()); // Start taskY
var z = DoMoreHeavyLiftingOnCurrentThread();
Task.WaitAll(taskX, taskY); // Wait for X and Y - the Task equivalent of `Thread.Join`
// Return a final object comprising data from the work done on all three tasks
return new Page(taskX.Result, taskY.Result, z);
}
以上可能需要三个线程,如果有足够的核心可以同时执行CPU绑定工作。
这与async / await
相反,DoThings
通常用于在等待I / O绑定调用完成时释放线程。假设public async Task<string> DoThings(int foo) {
var result = await SomeAsyncIo(foo);
return "done!";
}
确实受I / O约束,并且看起来像
public async Task<Page> ServePage() {
var task1 = DoThings(123); // Kick off Task 1
var task2 = DoThings(234); // Kick off Task 2 in parallel with task 1
await Task.WhenAll(task1, task2); // Wait for both tasks to finish, while releasing this thread
return new Page(task1.Result, task2.Result); // Return a result with data from both tasks
}
您可以并行执行但仍然异步执行:
await Task.WhenAll
如果I / O绑定工作花费合理的时间,则很有可能在DoThings
期间实际运行零线程-参见Stephen Cleary's article。
有第三个但非常危险的选择,那就是失火。由于方法async
已被标记为Task
,因此它已经返回了Task.Run
,因此根本不需要使用public Page ServePage() // No async
{
#pragma warning disable 4014 // warning is suppresed by the Pragma
DoThings(10); // Kick off DoThings but don't wait for it to complete.
#pragma warning enable 4014
// ... other code
return new Page();
}
。忘却了,看起来像这样:
await
根据@JohnWu的评论,“火灾后遗忘”方法很危险,通常表示有设计气味。有关here和here
的更多信息修改
回复:
有一个细微的差别使我一遍又一遍地逃脱,例如调用异步方法从同步方法返回Task的行为被抛弃了。 (这是最后一个代码示例。)我是否正确理解?
很难解释,但是无论在第一次等待之前,在调用的async
方法中是否使用同步关键字Task.Run
进行调用,无论是否使用Thread.Sleep
关键字除非我们求助于await Task.Delay
之类的锤子,否则它将在调用者的线程上执行。
也许此示例可能有助于理解(请注意,我们故意使用同步public async Task<Page> ServePage()
{
// Launched from this same thread,
// returns after ~2 seconds (i.e. hits both sleeps)
// continuation printed.
await DoThings(10);
#pragma warning disable 4014
// Launched from this same thread,
// returns after ~1 second (i.e. hits first sleep only)
// continuation not yet printed
DoThings(10);
// Task likely to be scheduled on a second thread
// will return within few milliseconds (i.e. not blocked by any sleeps)
Task.Run(() => DoThings(10));
// Task likely to be scheduled on a second thread
// will return after 2 seconds, although caller's thread will be released during the await
// Generally a waste of a thread unless also doing CPU bound work on current thread, or unless we want to release the calling thread.
await Task.Run(() => DoThings());
// Redundant state machine, returns after 2 seconds
// see return Task vs async return await Task https://stackoverflow.com/questions/19098143
await Task.Run(async () => await DoThings());
}
public async Task<string> DoThings(int foo) {
Thread.Sleep(1000);
var result = await SomeAsyncIo(foo);
Trace.WriteLine("Continuation!");
Thread.Sleep(1000);
return "done!";
}
而非await
来模拟CPU受限的工作并引入可以观察到的延迟)
String
还有另一点需要注意-在大多数情况下,不能保证在等待之后与streamsConfig.put(StreamsConfig.DEFAULT_TIMESTAMP_EXTRACTOR_CLASS_CONFIG,
"com.company.data.kstreams.Processor.MediaTimestampExtractor");
之前的同一线程上执行继续代码。继续代码将由编译器重新写入Task中,并且继续任务将在线程池中进行调度。
答案 1 :(得分:1)
此
Task.Run(() => DoThings(10));
将“并行”运行单独的任务,这意味着另一个线程将运行此任务。输入该方法的线程将继续执行下一条语句。
允许您在这里做什么。这就是为什么这是一个警告。 (我假设该方法的其余部分(未显示)返回Page
。)
该消息警告您,由于另一个任务正在另一个线程上执行,因此它可以在方法中的另一个代码之前,之后或同时执行。此方法不再“知道”任务在做什么或何时完成。
换句话说:
不要假设,因为这行代码将在要执行的方法中的其他代码行之前出现。如果此任务完成后需要执行某些操作,请在执行下一步之前等待它。
例如:
public int DoSomething()
{
var x = 1;
Task.Run(() => x++);
return x;
}
这返回什么?这取决于。它可以返回1或2。可以在x
递增之前或之后返回。如果您关心x
是否已增加,那么这很不好。如果您的任务执行的操作与方法的其余部分无关,而您根本不在乎任务是否在方法的其余部分之前或之后完成,则此警告对您而言无关紧要。如果您确实在乎,那么这很重要,并且您要等待任务。
答案 2 :(得分:1)
调用返回任务的异步方法时,可以做两件事:
等待任务,任务立即将控制权返回给调用方,并在任务完成时继续执行(有或没有返回值)。在这里您还可以捕获执行任务时可能发生的任何异常。
解雇。这是当您启动任务并将其完全删除(包括捕获异常的功能)时。最重要的是,除非等待,否则控制执行将超出调用范围,并且可能导致意外的状态损坏问题。
您在代码中排名第二。尽管在技术上允许,但由于上述原因,编译器警告您。
但是对您的问题-如果您真的只是想开枪而忘记了,为什么还要创建该显式任务?您可以直接调用DoThings(10),不是吗?除非我在代码中缺少我看不见的东西。 所以-你不能做到这一点吗?
public async Task<Page> ServePage() {
DoThings(10);
}
答案 3 :(得分:0)
以下所有版本均有效,无警告,或多或少等效:
public Task ServePage1()
{
return Task.Run(async () => await DoThings(10));
}
public async Task ServePage2()
{
await Task.Run(async () => await DoThings(10));
}
public Task ServePage3()
{
return Task.Run(() => DoThings(10));
}
public async Task ServePage4()
{
await Task.Run(() => DoThings(10));
}
通常,您不应忽略Task.Run
的返回值来执行即发即弃的任务。如果这样做,编译器将发出警告,因为它很少是有意的。