我有一个关于Webapi2的问题
我的应用程序完全是async/await
,但是我想优化最后一部分。我很难找到答案,所以有什么办法可以做以下事情?
一个webapi2 Controller 的示例:
private async Task<Foo> Barfoo(Bar foo)
{
//some async function
}
public async Task<IHttpActionResult> Foo(Bar bar)
{
List<Task> tasks=new List<Task>();
var actualresult=Barfoo(bar.Bar);
tasks.Add(actualresult);
foreach(var foobar in bar.Foo)
{
//some stuff which fills tasks
}
await Task.WhenAll(tasks);
return Ok(actualresult.Result);
}
客户端只需要一个功能,所以我想要的更多是这样的:
private async Task<Foo> Barfoo(Bar foo)
{
//some async function
}
public async Task<IHttpActionResult> Foo(Bar bar)
{
List<Task> tasks=new List<Task>();
var actualresult=Barfoo(bar.Bar);
return Ok(actualresult.Result);
foreach(var foobar in bar.Foo)
{
//some stuff which fills tasks for extra logic, not important for the client
}
await Task.WhenAll(tasks);
}
最佳答案
假设您正在尝试并行化 Controller Action 调用的许多异步任务,并假设您想在仅完成一个(确定的)任务后就将响应返回给客户端,而无需等待所有响应(即发即忘) ),您可以简单地调用异步方法而无需等待它们:
// Random async method here ...
private async Task<int> DelayAsync(int seconds)
{
await Task.Delay(seconds*1000);
Trace.WriteLine($"Done waiting {seconds} seconds");
return seconds;
}
[HttpGet]
public async Task<IHttpActionResult> ParallelBackgroundTasks()
{
var firstResult = await DelayAsync(6);
// Initiate unawaited background tasks ...
#pragma warning disable 4014
// Calls will return immediately
DelayAsync(100);
DelayAsync(111);
// ...
#pragma warning enable 4014
// Return first result to client without waiting for the background task to complete
return Ok(firstResult);
}
如果您需要在所有后台任务完成后进行进一步处理,即使原始请求线程已完成,也可以在完成后安排继续:
#pragma warning disable 4014
var backgroundTasks = Enumerable.Range(1, 5)
.Select(DelayAsync);
// Not awaited
Task.WhenAll(backgroundTasks)
.ContinueWith(t =>
{
if (t.IsFaulted)
{
// Exception handler here
}
Trace.WriteLine($"Done waiting for a total of {t.Result.Sum()} seconds");
});
#pragma warning restore 4014
更好的方法是将后台工作重构为自己的异步方法,该方法可以利用异常处理的好处:
private async Task ScheduleBackGroundWork()
{
try
{
// Initiate unawaited background tasks
var backgroundTasks = Enumerable.Range(1, 5)
.Select(DelayAsync);
var allCompleteTask = await Task.WhenAll(backgroundTasks)
.ConfigureAwait(false);
Trace.WriteLine($"Done waiting for a total of {allCompleteTask.Sum()} seconds");
}
catch (Exception)
{
Trace.WriteLine("Oops");
}
}
仍将等待后台工作的调用,即:
#pragma warning disable 4014
ScheduleBackGroundWork();
#pragma warning restore 4014
注释
Task.Run()
的优势在于它使用的线程池线程更少。 Trace.WriteLine
)都将需要一个线程来完成,这仍然可能导致如果所有延续同时完成,则会出现饥饿-由于可伸缩性的原因,您不希望多个客户端调用此类功能。 IDisposable
的依赖关系,则需要捏造您的容器以说服它不要处理这些依赖关系请求完成时(因为您的继续操作将来需要运行一段时间)编辑-重新伸缩
老实说,这将在很大程度上取决于您打算对“后台”任务执行的操作。考虑以下更新的“后台任务”:
private async Task<int> DelayAsync(int seconds)
{
// Case 1 : If there's a lot of CPU bound work BEFORE the innermost await:
Thread.Sleep(1000);
await Task.Delay(seconds*1000)
.ConfigureAwait(false);
// Case 2 : There's long duration CPU bound work in the continuation task
Thread.Sleep(1000);
Trace.WriteLine($"Done waiting {seconds} seconds");
return seconds;
}
await
(上面的案例1),您可能需要诉诸于乔纳森(Jonathan)的
Task.Run()
策略使等待的客户端脱钩从“案例1”工作访问Controller(否则客户端将被迫等待)。这样做将消耗每个任务约1个线程。
await
之后进行CPU密集型工作,则预定的连续性将消耗掉
剩余工作期间的线程。尽管这不会影响原始客户端调用的持续时间,但会影响整个进程线程和CPU使用率。
将工作卸载到某些外部IO绑定(bind)事件(例如数据库,外部Web服务等),而几乎没有或没有IO前后处理,那么Task的剩余部分将很快完成,线程使用率可以忽略不计。
因此,我猜答案是“取决于情况”。在自托管的Owin服务上,您可能不需要进行一些前/后处理,而无需执行一些未处理的任务,但是,如果您使用的是Azure,则Azure functions或更旧的Azure Web Jobs之类的声音听起来可能是更好的后台处理选择。
关于c# - Webapi2-完成一项任务后从 Controller 操作返回,但继续进行进一步的异步处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45626713/