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

C#.Net.Net 4.0Task Parallel-LibraryFire and-Forget

C# Problem Overview


I really like this question:

https://stackoverflow.com/questions/1018610/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?

C# Solutions


Solution 1 - C#

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 - C#

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 - C#

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 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathon KresnerView Question on Stackoverflow
Solution 1 - C#Chris MoschiniView Answer on Stackoverflow
Solution 2 - C#Ade MillerView Answer on Stackoverflow
Solution 3 - C#Mike StrobelView Answer on Stackoverflow
Solution 4 - C#Mert AkcakayaView Answer on Stackoverflow