做任务.运行规模以及使用示例WebApi中的任务
本文关键字:任务 WebApi 行规 运行 | 更新日期: 2023-09-27 18:07:30
我们的系统中有很多请求,所以我们使用带有WebApi的任务。在一些地方,我们对速度有很高的要求,所以我们不能等待任务完成,我为此创建了一个Worker。它创建了一个嵌套的容器,这样实体框架、DbContext就不会被处理掉等等。但它看起来像任务。每次运行都会生成一个新线程,这个规模会有多好?
public class BackgroundWorker<TScope> : IBusinessWorker<TScope>, IRegisteredObject where TScope : class
{
private readonly IBusinessScope<TScope> _scope;
private bool _started;
private bool _stopping;
public BackgroundWorker(IBusinessScope<TScope> scope)
{
_scope = scope;
}
public void Run(Func<TScope, Task> action)
{
if(_stopping) throw new Exception("App pool is recycling, cant queue work");
if(_started) throw new Exception("You cant call Run multiple times");
_started = true;
HostingEnvironment.RegisterObject(this);
Task.Run(() =>
action(_scope.EntryPoint).ContinueWith(t =>
{
_scope.Dispose();
HostingEnvironment.UnregisterObject(this);
}));
}
public void Stop(bool immediate)
{
_stopping = true;
if(immediate)
HostingEnvironment.UnregisterObject(this);
}
}
使用backgroundWorker.Run(async ctx => await ctx.AddRange(foos).Save());
如果我谷歌他们最终都使用Task.Run
,但这不是杀死目的吗?
更新:做过测试吗
var guid = Guid.NewGuid();
_businessWorker.Run(async ctx => {
System.Diagnostics.Debug.WriteLine("{0}: {1}", guid, Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
System.Diagnostics.Debug.WriteLine("{0}: {1}", guid, Thread.CurrentThread.ManagedThreadId);
});
这个输出3bdbe90b-c31e-4709-95d8-f7516210b0ac: 17
3bdbe90b-c31e-4709-95d8-f7516210b0ac: 9
6548fd26-d209-4427-9a91-40fc30aa509e: 15
6548fd26-d209-4427-9a91-40fc30aa509e: 19
7411b043-4fae-44bf-b93f-4273a532afa1: 7
7411b043-4fae-44bf-b93f-4273a532afa1: 17
这表明Task.Run
实际上像我想的那样工作
对于真正的DB代码,它看起来像这样
a939713d-d728-46c9-be33-aa57704cf242: 19 <--
a939713d-d728-46c9-be33-aa57704cf242: 19 <-- Used same for entire work
7e588a42-afd0-4ab5-ba6b-f8520c889cde: 7
7e588a42-afd0-4ab5-ba6b-f8520c889cde: 19 <-- Reused first works thread when work #2 continued
6f3b067f-f478-43f9-8411-8142b449c28b: 8
6f3b067f-f478-43f9-8411-8142b449c28b: 18
更新:尝试过Luaan的方法,似乎与从EntityFramework或WebApi HttpClient衍生的任务一起工作,但是像下面这样的手动任务等不能很好地工作,有些是执行的,有些不是。Task.Run
时全部执行
_businessWorkerFactory().Run(async ctx =>
{
var guid = Guid.NewGuid();
System.Diagnostics.Debug.WriteLine("{0}: {1}", guid, Thread.CurrentThread.ManagedThreadId);
var completion = new TaskCompletionSource<bool>();
ThreadPool.QueueUserWorkItem(obj =>
{
Thread.Sleep(1000);
completion.SetResult(true);
});
await completion.Task;
System.Diagnostics.Debug.WriteLine("{0}: {1}", guid, Thread.CurrentThread.ManagedThreadId);
});
Task.Run
调度任务在线程池线程上运行。处理请求的线程池。
在ASP上。. NET应用程序,将工作发送到线程池会窃取处理请求所需的线程。
考虑到您的需求,我认为您最好使用MSMQ之类的东西来排队工作到另一个服务/进程。
Task.Run
不会生成一个新线程——它从线程池中借用了一个线程(假设线程池任务调度器——有不同的调度器,您也可以编写自己的调度器)。当你在Task.Run
中使用await
时,它仍然会像往常一样工作-释放线程池线程,直到发布回调。
然而,正是由于这个原因,使用Task.Run
进行I/O工作几乎没有意义。如果你有异步I/O要做,那就去做吧——它的工作原理是一样的,不需要上下文切换。你必须使它异步-如果它只是阻塞代码,你会占用线程池中有价值的线程。
注意,您不需要来完成异步请求。如果您正在执行的异步操作不需要太多时间来设置(也就是说,它几乎立即返回Task
,即使它还没有完成),您可以调用它:
public async Task SomeAsync()
{
var request = new MyRequest();
await request.MakeRequestAsync();
...
}
public void Start()
{
var task = SomeAsync();
// Now the task is started, and we can use it for future reference. Or just wire up
// some error handling continuations etc. - though it's usually a better idea to do that
// within SomeAsync directly.
}