Simplest way to do a fire and forget method in c# 4.0

82,542

Solution 1

Not an answer for 4.0, but worth noting that in .Net 4.5 you can make this even simpler with:

#pragma warning disable 4014
Task.Run(() =>
{
    MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

The pragma is to disable the warning that tells you you're running this Task as fire and forget.

If the method inside the curly braces returns a Task:

#pragma warning disable 4014
Task.Run(async () =>
{
    await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

Let's break that down:

Task.Run returns a Task, which generates a compiler warning (warning CS4014) noting that this code will be run in the background - that's exactly what you wanted, so we disable warning 4014.

By default, Tasks attempt to "Marshal back onto the original Thread," which means that this Task will run in the background, then attempt to return to the Thread that started it. Often fire and forget Tasks finish after the original Thread is done. That will cause a ThreadAbortException to be thrown. In most cases this is harmless - it's just telling you, I tried to rejoin, I failed, but you don't care anyway. But it's still a bit noisy to have ThreadAbortExceptions either in your logs in Production, or in your debugger in local dev. .ConfigureAwait(false) is just a way of staying tidy and explicitly say, run this in the background, and that's it.

Since this is wordy, especially the ugly pragma, I use a library method for this:

public static class TaskHelper
{
    /// <summary>
    /// Runs a TPL Task fire-and-forget style, the right way - in the
    /// background, separate from the current thread, with no risk
    /// of it trying to rejoin the current thread.
    /// </summary>
    public static void RunBg(Func<Task> fn)
    {
        Task.Run(fn).ConfigureAwait(false);
    }

    /// <summary>
    /// Runs a task fire-and-forget style and notifies the TPL that this
    /// will not need a Thread to resume on for a long time, or that there
    /// are multiple gaps in thread use that may be long.
    /// Use for example when talking to a slow webservice.
    /// </summary>
    public static void RunBgLong(Func<Task> fn)
    {
        Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
            .ConfigureAwait(false);
    }
}

Usage:

TaskHelper.RunBg(async () =>
{
    await doSomethingAsync();
}

Solution 2

With the Task class yes, but PLINQ is really for querying over collections.

Something like the following will do it with Task.

Task.Factory.StartNew(() => FireAway());

Or even...

Task.Factory.StartNew(FireAway);

Or...

new Task(FireAway).Start();

Where FireAway is

public static void FireAway()
{
    // Blah...
}

So by virtue of class and method name terseness this beats the threadpool version by between six and nineteen characters depending on the one you choose :)

ThreadPool.QueueUserWorkItem(o => FireAway());

Solution 3

I have a couple issues with the leading answer to this question.

First, in a true fire-and-forget situation, you probably won't await the task, so it is useless to append ConfigureAwait(false). If you do not await the value returned by ConfigureAwait, then it cannot possibly have any effect.

Second, you need to be aware of what happens when the task completes with an exception. Consider the simple solution that @ade-miller suggested:

Task.Factory.StartNew(SomeMethod);  // .NET 4.0
Task.Run(SomeMethod);               // .NET 4.5

This introduces a hazard: if an unhandled exception escapes from SomeMethod(), that exception will never be observed, and may1 be rethrown on the finalizer thread, crashing your application. I would therefore recommend using a helper method to ensure that any resulting exceptions are observed.

You could write something like this:

public static class Blindly
{
    private static readonly Action<Task> DefaultErrorContinuation =
        t =>
        {
            try { t.Wait(); }
            catch {}
        };

    public static void Run(Action action, Action<Exception> handler = null)
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        var task = Task.Run(action);  // Adapt as necessary for .NET 4.0.

        if (handler == null)
        {
            task.ContinueWith(
                DefaultErrorContinuation,
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
        else
        {
            task.ContinueWith(
                t => handler(t.Exception.GetBaseException()),
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

This implementation should have minimal overhead: the continuation is only invoked if the task does not complete successfully, and it should be invoked synchronously (as opposed to being scheduled separately from the original task). In the "lazy" case, you won't even incur an allocation for the continuation delegate.

Kicking off an asynchronous operation then becomes trivial:

Blindly.Run(SomeMethod);                              // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e));  // Log error

1. This was the default behavior in .NET 4.0. In .NET 4.5, the default behavior was changed such that unobserved exceptions would not be rethrown on the finalizer thread (though you may still observe them via the UnobservedTaskException event on TaskScheduler). However, the default configuration can be overridden, and even if your application requires .NET 4.5, you should not assume that unobserved task exceptions will be harmless.

Solution 4

Just to fix some issue that will happen with Mike Strobel's answer:

If you use var task = Task.Run(action) and after assign a continuation to that task, then you will run into a risk of Task throwing some exception before you assign an exception handler continuation to the Task. So, the class below should be free of this risk:

using System;
using System.Threading.Tasks;

namespace MyNameSpace
{
    public sealed class AsyncManager : IAsyncManager
    {
        private Action<Task> DefaultExeptionHandler = t =>
        {
            try { t.Wait(); }
            catch { /* Swallow the exception */ }
        };

        public Task Run(Action action, Action<Exception> exceptionHandler = null)
        {
            if (action == null) { throw new ArgumentNullException(nameof(action)); }

            var task = new Task(action);

            Action<Task> handler = exceptionHandler != null ?
                new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
                DefaultExeptionHandler;

            var continuation = task.ContinueWith(handler,
                TaskContinuationOptions.ExecuteSynchronously
                | TaskContinuationOptions.OnlyOnFaulted);
            task.Start();

            return continuation;
        }
    }
}

Here, the task is not run directly, instead it is created, a continuation is assigned, and only then the task is run to eliminate the risk of the task completing the execution (or throwing some exception) before assigning a continuation.

The Run method here returns the continuation Task so I am able to write unit tests making sure the execution is complete. You can safely ignore it in your usage though.

Share:
82,542

Related videos on Youtube

Aryan upadhyay
Author by

Aryan upadhyay

Rent me by the hour at AirPair{}Me/hackerpreneur for live pair programming Backbone.js Help. My blog http://hackerpreneurialism.com/ My business http://www.airpair.com/

Updated on July 08, 2022

Comments

  • Aryan upadhyay
    Aryan upadhyay almost 2 years

    I really like this question:

    Simplest way to do a fire and forget method in C#?

    I just want to know that now that we have Parallel extensions in C# 4.0 is there a better cleaner way to do Fire & Forget with Parallel linq?

    • Brian Rasmussen
      Brian Rasmussen about 13 years
      The answer to that question still applies to .NET 4.0. Fire and forget doesn't get much simpler than QueueUserWorkItem.
    • Michael Freidgeim
      Michael Freidgeim almost 3 years
      Does this answer your question? Simplest way to do a fire and forget method in C#?
  • Aryan upadhyay
    Aryan upadhyay about 13 years
    Surely they are not functionality equivalent though?
  • Ade Miller
    Ade Miller about 13 years
    There is a subtle semantic difference between StartNew and new Task.Start but otherwise, yes. They all queue FireAway to run on a thread in the threadpool.
  • Keith Morgan
    Keith Morgan over 9 years
    If MyFireAndForgetMethod is already marked async (in which case it should be called MyFireAndForgetMethodAsync by convention), the code is even simpler: #pragma warning disable 4014 MyFireAndForgetMethodAsync();
  • Chris Moschini
    Chris Moschini over 9 years
    @ksm Your approach is in for some trouble unfortunately - have you tested it? That approach is exactly why Warning 4014 exists. Calling an async method without await, and without the help of Task.Run... will cause that method to run, yes, but once it finishes it will attempt to marshal back to the original thread it was fired from. Often that thread will have already completed execution and your code will explode in confusing, indeterminate ways. Don't do it! The call to Task.Run is a convenient way of saying "Run this in the global context," leaving nothing for it to attempt to marshal to.
  • Keith Morgan
    Keith Morgan over 9 years
    If that's the case, why does Task.Run generate warning 4014?
  • Keith Morgan
    Keith Morgan over 9 years
    It appears that the proper solution to not marshal back to the original thread is to call ConfigureAwait, so I think the simplest solution to fire and forget a method marked async is #pragma warning disable 4014 MyFireAndForgetMethodAsync().ConfigureAwait(false); @StephenCleary
  • Edward Brey
    Edward Brey over 8 years
    What's wrong with plain old Task.Run(() => DoIt(...));? I don't get any compiler warning.
  • Edward Brey
    Edward Brey over 8 years
    @NicholasPetersen Perhaps the issue is specific to older versions of C#. I was using C# 6, and just tried it again, and still don't see a warning.
  • Nicholas Petersen
    Nicholas Petersen over 8 years
    ChrisMoschini - I am showing you don't need the pragma warning disablers within your RunBg function (it is after all a void return). Furthermore, you are calling it with an async anonymous func like this: RunBg(async () => await doSomethingAsync());, I am showing all you need is RunBg(() => doSomethingAsync());. But I still don't feel good about all of this so I would love some discussion / response to that, though it works in my code. @EdwardBrey Interesting, but I am using C# 6 / .NET 4.6 as well.
  • Chris Moschini
    Chris Moschini over 8 years
    @NicholasPetersen Tested and you are correct! The code's been through a lot of iterations so at this point those were kind of leftovers. I updated the code sample and added one we've been using a lot, as a bonus.
  • Nicholas Petersen
    Nicholas Petersen over 8 years
    @ChrisMoschini glad to help, and thanks for the update! On the other matter, where I wrote "you are calling it with an async anonymous func" in the comment above, it's honestly confusing to me which is the truth. All I know is the code works when the async is not included in the calling (anonymous function) code, but would that mean the calling code would not be run in an asynchronous way (which is bad, a very bad gotcha)? So I am not recommending without the async, it's just odd that in this case, both work.
  • Chris Moschini
    Chris Moschini over 8 years
    Anything that returns Task can be awaited, and will be handled asynchronously (as long as it's called properly). async/await are a pair of syntactic sugar that are simply used to ask the compiler to break up your async method into multiple parts around its awaits. They have no bearing on the caller - the caller can await a non-async method that simply returns Task. In my await doSomethingAsync, I mark the Func async because people may want to await multiple calls inside the Func. But it is true the trivial example could be rewritten without the async keyword.
  • Chris Marisic
    Chris Marisic over 8 years
    You probably should have try/catch around the functions
  • Alexey Strakh
    Alexey Strakh about 8 years
    as long as it fire and forget I also want to handle errors if any. Before I was using ContinueWith(t=> Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted) but not with ConfigureAwait I cannot. Please advise
  • Chris Moschini
    Chris Moschini about 8 years
    @Alexey Strakh Since it's fire and forget those errors need to be handled out on that other thread. Best way to do that is the way you know - wrap the inner call in try/catch.
  • Alexey Strakh
    Alexey Strakh about 8 years
    I cannot rely on devs and verify every method that it's handled properly so I prefer using the extension method with ContinueWith(t=> Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted) which guarantees me that error (if any) will be logged. Moreover even fire and forget method could be awaited because it's async Task so I wouldn't like to put extra exception handling logic there
  • Chris Moschini
    Chris Moschini about 8 years
    @AlexeyStrakh Think you're overcomplicating this. You literally just need to put try/catch in there. No need to rely on other devs. Task.Run(async () => { try { await MyFireAndForgetMethod(); } catch(... {...} }).ConfigureAwait(false);
  • Alexey Strakh
    Alexey Strakh about 8 years
    Oh I got it, my intent was different but solution works fine.
  • Mike Strobel
    Mike Strobel almost 8 years
    I do not see the point of ConfigureAwait(false) on Task.Run when you do not await the task. The purpose of the function is in its name: "configure await". If you do not await the task, you do not register a continuation, and there is no code for the task to "marshal back onto the original thread", as you say. The bigger risk is of an unobserved exception being rethrown on the finalizer thread, which this answer does not even address.
  • Stack Undefined
    Stack Undefined over 7 years
    If a handler is passed in and the ContinueWith was invoked due to cancellation, the antecedent's exception property will be Null and a null reference exception will be thrown. Setting the task continuation option to OnlyOnFaulted will eliminate the need for the null check or check if the exception is null before using it.
  • stricq
    stricq about 7 years
    Be very careful with anything that looks like this: Task.Run(async () => await something()); The pattern sets up a void method which will crash the runtime if an unhandled exception occurs.
  • stricq
    stricq about 7 years
    The Run() method needs to be overridden for different method signatures. Better to do this as an extension method of Task.
  • stricq
    stricq about 7 years
  • Mike Strobel
    Mike Strobel about 7 years
    @stricq The question asked about "fire and forget" operations (i.e., status never checked and result never observed), so that's what I focused on. When the question is how to most cleanly shoot oneself in the foot, debating which solution is "better" from a design perspective becomes rather moot :). The best answer is arguably "don't", but that fell short of the minimum answer length, so I focused on providing a concise solution that avoids issues present in other answers. Arguments are easily wrapped in a closure, and with no return value, using Task becomes an implementation detail.
  • Mike Strobel
    Mike Strobel about 7 years
    @stricq That said, I do not disagree with you. The alternative you proposed is exactly what I've done in the past, albeit for different reasons. "I want to avoid crashing the app when a developer fails to observe a faulted Task" is not the same as "I want a simple way to kick off a background operation, never observe its result, and I don't care what mechanism is used to do it." On that basis, I stand behind my answer to the question that was asked.
  • Chris Moschini
    Chris Moschini about 7 years
    @stricq There are no async void uses here. If you're referring to async () => ... the signature is a Func that returns a Task, not void.
  • PreguntonCojoneroCabrón
    PreguntonCojoneroCabrón about 6 years
    fire and forget in ASP.NET WebForms and windows.close() ?
  • PreguntonCojoneroCabrón
    PreguntonCojoneroCabrón about 6 years
    Working for this case: fire and forget in ASP.NET WebForms and windows.close() ?
  • PreguntonCojoneroCabrón
    PreguntonCojoneroCabrón about 6 years
    Working for this case: fire and forget in ASP.NET WebForms and windows.close() ?
  • Altiano Gerung
    Altiano Gerung almost 6 years
    On VB.net to supress warning: #Disable Warning BC42358
  • NSN
    NSN over 5 years
    In .NET 4.6.1 Error CS4008 Cannot await 'void' error
  • Deantwo
    Deantwo about 4 years
    Is it also intentional that you didn't make it a static class?
  • Mert Akcakaya
    Mert Akcakaya about 4 years
    This class implements IAsyncManager interface, so it cannot be a static class.
  • Pablo Retyk
    Pablo Retyk about 4 years
    In C# 7 you can use Discards (_) and there is no need to supress warnings
  • Rhys Jones
    Rhys Jones over 3 years
    I don't think the situation this is addressing (task faults before continuation is added) matters. If this happens the faulted state and the exception are stored in the task and will be handled by the continuation when it is attached. The important point to understand is that the exception is not thrown until an await task/task.Wait()/task.Result/task.GetAwaiter().GetResult() - see docs.microsoft.com/en-us/dotnet/standard/parallel-programmin‌​g/….
  • Michael Freidgeim
    Michael Freidgeim almost 3 years
    Do not Swallow the exception, log it.
  • Michael Freidgeim
    Michael Freidgeim almost 3 years
    Do not Swallow the exception in catch {} , log it.
  • Mike Strobel
    Mike Strobel almost 3 years
    Probably shouldn't call it Run if it doesn't actually run the task :).
  • Mert Akcakaya
    Mert Akcakaya almost 3 years
    It does run the task :)