可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a Web API's action where I need to run some task and forget about this task.
This is how my method is organized now:
public async Task<SomeType> DoSth()
{
await Task.Run(...);
.....
//Do some other work
}
The thing is that obviously it stops at the await line waiting when it's done and only then continues the work.
And I need to "fire and forget"
Should I just call Task.Run() without any async-await?
回答1:
And I need to "fire and forget"
I have a blog post that goes into details of several different approaches for fire-and-forget on ASP.NET.
In summary: first, try not to do fire-and-forget at all. It's almost always a bad idea. Do you really want to "forget"? As in, not care whether it completes successfully or not? Ignore any errors? Accept occasional "lost work" without any log notifications? Almost always, the answer is no, fire-and-forget is not the appropriate approach.
A reliable solution is to build a proper distributed architecture. That is, construct a message that represents the work to be done and queue that message to a reliable queue (e.g., Azure Queue, MSMQ, etc). Then have an independent backend that process that queue (e.g., Azure WebJob, Win32 service, etc).
Should I just call Task.Run() without any async-await?
No. This is the worst possible solution. If you must do fire-and-forget, and you're not willing to build a distributed architecture, then consider Hangfire. If that doesn't work for you, then at the very least you should register your cowboy background work with the ASP.NET runtime via HostingEnvironment.QueueBackgroundWorkItem
or my ASP.NET Background Tasks library. Note that QBWI and AspNetBackgroundTasks are both unreliable solutions; they just minimize the chance that you'll lose work, not prevent it.
回答2:
True fire and forget tasks can be difficult in asp.net as they can often die along with the request that they were created as part of.
If you are using 4.5.2+ then you can use QueueBackgroundWorkItem to run the task. By registering tasks via this method the AppDomain will try to delay shutting down until they have all completed but there can still be instances when they will be killed before they are completed. This is probably the simplest thing to do but worth reading into to see exactly what instances can cause jobs to be cancelled.
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await Task.Run(...);
});
There is an tool called hangfire that uses a persistent store to ensure that a task has completed and has built-in retry and error recording functionality. This is more for "background tasks" but does suit fire and forget. This is relatively easy to setup and offers a variety of backing stores, I can't recall the exact details but some require a license and some don't (like MSSQL).
回答3:
For fire and forget, use this
Task.Factory.StartNew(async () =>
{
using (HttpClient client = new HttpClient())
{
await client.PostAsync("http://localhost/api/action", new StringContent(""));
}
});
回答4:
Never fire and forget, because then you won't get to see any errors, which makes for some very awkward troubleshooting if something goes wrong (having the task method do its own exception handling isn't guaranteed to work, because the task may not successfully start in the first place). Unless you really don't mind if the task does anything or not, but that's quite unusual (since, if you truly didn't care, why run the task in the first place)? At the very least, create your task with a continuation:
Task.Run(...)
.ContinueWith(t =>
logException(t.Exception.GetBaseException()),
TaskContinuationOptions.OnlyOnFaulted
)
;
You can make this more sophisticated as your needs dictate.
In the specific case of a web API, you may actually want to wait for your background tasks to finish before you complete your request. If you don't, you're leaving stuff running in the background that may misrepresent how much load your service can really take, or even stop working altogether if clients fire too many requests and you don't do anything to throttle them. You can gather tasks up and issue an await Task.WhenAll(...)
at the end to achieve that; this way, you can continue to do useful work while your background tasks plod away, but you don't return until everything's done.
回答5:
I use HangFire.
This is best for me.
An easy way to perform background processing in .NET and .NET Core applications. No Windows Service or separate process required.
Backed by persistent storage. Open and free for commercial use.
回答6:
I agree with others that you should not just forget about your call. However, to answer your question, if you remove await from the Task.Run() line, the call will not be blocking as shown here
public async Task<SomeType> DoSth()
{
Task.Run(...);
.....
//Do some other work while Task.Run() continues in parallel.
}
回答7:
There are a few posts here which push the “Never fire and forget”, its wrong etc.
The truth is it’s not wrong from certain points of view. Rather misdescribed in some ways.
What this should really say is “the user should be able to fire and forget, but it should still tell the developer / owner.”
A good example is a contact us form on a website. The customer fills in the form and behind the scenes it emails the owner of the site. Some email servers take far to long to process the send, so packages like Hangfire (which is what I would use) send it off to another process outside of the webserver "thread".
Yes, this should advise the developer / owner somehow (and keep the contact details) if an error occurs. But in no way should this inform the potential contact about the issue. (unless you want to lose a potential customer)
回答8:
For invoking a fire and forget WebApi method, I used the following code to ensure that it returns an OK response. I my case, the bearer authorization token created at login is stored in a cookie:
...
FireAndForget().Wait();
...
private async Task FireAndForget()
{
using (var httpClient = new HttpClient())
{
HttpCookie cookie = this.Request.Cookies["AuthCookieName"];
var authToken = cookie["AuthTokenPropertyName"] as string;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
using (var response = await httpClient.GetAsync("http://localhost/api/FireAndForgetApiMethodName"))
{
//will throw an exception if not successful
response.EnsureSuccessStatusCode();
}
}
}