Asynchronous Fire and Forget With Lambdas

I’ve been having trouble getting to sleep lately, so I thought last night that I would put that to use and hack on Subtext a bit. While doing so, I ran into an old Asynchronous Fire and Forget helper method written way back by Mike Woodring which allows you to easily call a delegate asynchronously.

On the face of it, it seems like you could simply call BeginInvoke on the delegate and be done with it, Mike’s code addresses a concern with that approach:

Starting with the 1.1 release of the .NET Framework, the SDK docs now carry a caution that mandates calling EndInvoke on delegates you've called BeginInvoke on in order to avoid potential leaks. This means you cannot simply "fire-and-forget" a call to BeginInvoke without the risk of running the risk of causing problems.

The mandate he’s referring to, I believe, is this clause in the MSDN docs:

No matter which technique you use, always call EndInvoke to complete your asynchronous call.

Note that it doesn’t explicitly say “or you will get a memory leak”. But a little digging turns up the following comment in the MSDN forums.

The reason that you should call EndInvoke is because the results of the invocation (even if there is no return value) must be cached by .NET until EndInvoke is called.  For example if the invoked code throws an exception then the exception is cached in the invocation data.  Until you call EndInvoke it remains in memory.  After you call EndInvoke the memory can be released.  For this particular case it is possible the memory will remain until the process shuts down because the data is maintained internally by the invocation code.  I guess the GC might eventually collect it but I don't know how the GC would know that you have abandoned the data vs. just taking a really long time to retrieve it.  I doubt it does.  Hence a memory leak can occur.

The thread continues to have some back and forth and doesn’t appear to be conclusive either way, but this post by Don Box gives a very pragmatic argument.

…the reality is that some implementations rely on the EndXXX call to clean up resources.  Sometimes you can get away with it, but in the general case you can't.

In other words, why take the chance? In any case, much of this discussion is made redundant with the C# 3.0 Action class combined with ThreadPool.QueueUserWorkItem aka (QUWI)

Here is the code in Subtext for sending email, more or less. I have to define a delegate and then pass that to FireAndForget

// declaration
delegate bool SendEmailDelegate(string to, 
      string from, 
      string subject, 
      string body);

//... in a method body
SendEmailDelegate sendEmail = im.Send;
AsyncHelper.FireAndForget(sendEmail, to, from, subject, body);

This code relies on the FireAndForget method which I show here. Note I am not showing the full code. I just wanted to point out that the arguments to the delegate are not strongly typed. They are simply an array of objects which provide no guidance to how many arguments you need to pass.

public static void FireAndForget(Delegate d, params object[] args)
{
  ThreadPool.QueueUserWorkItem(dynamicInvokeShim, 
    new TargetInfo(d, args));
}

Also notice that this implementation uses QUWI under the hood.

With C# 3.0, there is no need to abstract away the call to QUWI. Just pass in a lambda, which provides the benefit that you’re calling the actual method directly so you get Intellisense for the argumennts etc… So all that code gets replaced with:

ThreadPool.QueueUserWorkItem(callback => im.Send(to, from, subject, body));

Much cleaner and I get to get rid of more code! As I’ve said before, the only thing better than writing code is getting rid of code!

Technorati Tags: ,,

What others have said

Requesting Gravatar... Johannessen Jan 09, 2009 11:14 AM
# re: Asynchronous Fire and Forget With Lambdas
I you are into post sharp, then you will get away with this clean solution :)


public class FireAndForgetAttribute : OnMethodInvocationAspect
{
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
{
ThreadPool.QueueUserWorkItem(delegate { eventArgs.Proceed(); });
}
}


and use it like:

...
[FireAndForget]
public bool SendEmailDelegate(string to, string from,
string subject, string body)
{
...
}
...


:)
Requesting Gravatar... Alex Jan 09, 2009 12:44 PM
# re: Asynchronous Fire and Forget With Lambdas
Is there any reason this wouldn't work in a similar manner, even in .Net 2.0?

string to, from, subject, body;
SendEmailDelegate sendEmail = im.Send;

ThreadPool.QueueUserWorkItem(delegate {
sendEmail(to, from, subject, body);
});
Requesting Gravatar... haacked Jan 09, 2009 1:40 PM
# re: Asynchronous Fire and Forget With Lambdas
@Johannessen Nice AOP approach to the problem! :)

@Alex True! I think the code I was refactoring was written in the 1.1 days and I completely skipped a 2.0 version to jump right to how I would do it today. :)

Requesting Gravatar... Stuart Jan 09, 2009 5:10 PM
# re: Asynchronous Fire and Forget With Lambdas
Great tip, I love lambdas too!

Just a small typo at the end I think

Thread.QueueUserWorkItem(callback => im.Send(to, from, subject, body));

Should be

ThreadPool.QueueUserWorkItem(callback => im.Send(to, from, subject, body));
Requesting Gravatar... Morgan Cheng Jan 09, 2009 6:54 PM
# re: Asynchronous Fire and Forget With Lambdas
IMHO, If BeginInvoke throws a exception, then no need to call EndInvoke.
Requesting Gravatar... Justin Chase Jan 10, 2009 7:47 AM
# re: Asynchronous Fire and Forget With Lambdas
I hate to be a downer but I'm going to have to say this is a bad idea. Honestly, when do you ever want to fire and forget? Your example there is about sending an email message, you really aught to be baking in some mechanism for handling exceptions otherwise this is an equivalent crime to catch(Exception) { }

How about:


delegate void AsyncResult<TResult>(TResult result, Exception error);
delegate void AsyncResult(Exception error);

delegate bool SendEmailDelegate(string to,
string from,
string subject,
string body);

public static void Fire(this Delegate d, AsyncResult result, params object[] args)
{
ThreadPool.QueueUserWorkItem(() =>
{
Exception error = null;
try
{
dynamicInvokeShim();
}
catch(Exception ex)
{
error = ex;
}
finally
{
result(ex);
}
},
new TargetInfo(d, args));
}


Then have another overload for the AsyncResult<TResult> delegate for methods with expected return results. Call it like this:


// adding "this" to the method makes it an extension method
im.Send.Fire(e => { if(e!=null) Log.Error(e); },
to, from, subject, body);


If you had a nice static log method that was smart enough to check the null for you you could pass that instead of a lambda also.
Requesting Gravatar... JD Conley Jan 10, 2009 6:38 PM
# re: Asynchronous Fire and Forget With Lambdas
<async_propganda>

As a scalability/performance junkie (and a subtext user) I'd like to say that if you're sending any quantity of email all of these are bad ideas. :) Using QueueUserWorkItem to create fake asynchronicity in an asp.net application is just asking for the "Server too Busy" error. What you gain in short term user pleasure you lose in crunch time scalability as you now use up 2x the limited, precious, worker threads. And besides, threads should be processing, not waiting for IO!

Waiting around for an email to finish sending (or any IO) in an extra worker threadpool thread takes away from the amount of threads you have available for rendering asp.net pages and handling ado.net calls. In the scalability sense, if you have to do IO synchronously you might as well just do it in the original asp.net thread since QueueUserWorkItem uses the same threadpool as your asp.net page processing.

The best option here is to use the SendAsync method provided on the System.Net.Mail.SmtpClient class and provide a lambda on the SendCompleted event. This does real asynchronous processing using async IO and IOCP behind the scenes. If you want the page to finish processing rather than go into async rendering mode you can temporarily remove the asp.net async context with System.Threading.SynchronizationContext.SetSynchronizationContext(null). I have this little helper class that does it:

using System;
using System.Threading;

namespace Hive7.Core
{
public class SynchronizationContextSwitcher : IDisposable
{
private SynchronizationContext _oldContext;

public SynchronizationContextSwitcher() : this(null)
{
}

public SynchronizationContextSwitcher(SynchronizationContext context)
{
_oldContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(context);
}

#region IDisposable Members

public void Dispose()
{
SynchronizationContext.SetSynchronizationContext(_oldContext);
_oldContext = null;
}

#endregion
}
}

If you can't use the SmtpClient class for some reason and are stuck with the synchronous mail sending option, you should spawn a single long running thread in application_start that polls a thread safe queue, and post to that queue for work, or use some threadpool other than the built-in worker one.

</async_propganda>
Requesting Gravatar... Florian Krüsch Jan 11, 2009 6:30 AM
# re: Asynchronous Fire and Forget With Lambdas
Keep in mind that apart from throwing an exception, QueueUserWorkItem could just return "false".
Requesting Gravatar... haacked Jan 11, 2009 3:17 PM
# re: Asynchronous Fire and Forget With Lambdas
@JDConley one thing we do is use a custom threadpool which doesn't take a thread from the ASP.NET ThreadPool (which is the same threadpool used when using the async programming model AFAIK).
Requesting Gravatar... PK Jan 11, 2009 5:15 PM
# re: Asynchronous Fire and Forget With Lambdas
@Haacked: can u please elaborate on this using code snippets: "use a custom threadpool which doesn't take a thread from the ASP.NET ThreadPool". please?
Requesting Gravatar... JD Conley Jan 11, 2009 11:52 PM
# re: Asynchronous Fire and Forget With Lambdas
@haacked: The async programming model exposed by the framework does things very differently than simply blocking a (worker pool or otherwise) thread behind the scenes. In fact the entire point is to not use a thread unless it's doing CPU intensive things rather than waiting on IO operations.

In any case. What threadpool are you using? I just saw QueueUserWorkItem.
Requesting Gravatar... Richard Jan 12, 2009 7:42 AM
# re: Asynchronous Fire and Forget With Lambdas
@JDConley: The Disposed method of your SynchronizationContextSwitcher class should be checking to make sure that it's running on the same thread as the constructor, and that the current context hasn't been changed since the constructor was called.

private ExecutionContext _executionContext;
private readonly SynchronizationContext _oldContext;
private readonly SynchronizationContext _newContext;

public SynchronizationContextSwitcher(SynchronizationContext context)
{
_newContext = context;
_executionContext = Thread.CurrentThread.ExecutionContext;
_oldContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(context);
}

public void Dispose()
{
if (null != _executionContext)
{
if (_executionContext != Thread.CurrentThread.ExecutionContext)
{
throw new InvalidOperationException("Dispose called on wrong thread.");
}
if (_newContext != SynchronizationContext.Current)
{
throw new InvalidOperationException("The SynchronizationContext has changed.");
}

SynchronizationContext.SetSynchronizationContext(_oldContext);
_executionContext = null;
}
}
Requesting Gravatar... Brian Chiasson Jan 14, 2009 1:28 PM
# re: Asynchronous Fire and Forget With Lambdas
@Haacked - I believe JDConley is right, perhaps you can get to the bottom of this and get back to us. According to the ThreadPool documentation (link below) there is only one ThreadPool available per process. So, if you are in a web farm or web garden you probably won't see too much of a problem - multiple ASP.NET worker processes. But if you have a single IIS site, then you are consuming ASP.NET threads to send out e-mail. Someone correct me if I am wrong please.
Requesting Gravatar... Brian Chiasson Jan 14, 2009 1:35 PM
# re: Asynchronous Fire and Forget With Lambdas
I knew I was going to forget the URL.

msdn.microsoft.com/.../...hreading.threadpool.aspx
Requesting Gravatar... Jarrett Jan 14, 2009 2:06 PM
# re: Asynchronous Fire and Forget With Lambdas
Taking a look at the Oxite source code, they create background services which all fire periodically using a System.Threading.Timer. So, as long as each background service doesn't fire off any additional threads, that will be only two threads for the SendMessages background service. However, the downfall is that each timer eats up a thread from the pool. If there are 10 background services, thats the potential for 20+ fewer threads in the pool at all times. So this sounds like a good solution as long as there are only a couple background services?
Requesting Gravatar... haacked Jan 14, 2009 2:52 PM
# re: Asynchronous Fire and Forget With Lambdas
@Brian Yes, there's only one System.Thread.Threading.ThreadPool per process. But you can write you're own thread pool.

Subtext uses this one: www.koders.com/.../...6D2DA45BA3D502389B154CB.aspx
Requesting Gravatar... Brian Chiasson Jan 14, 2009 3:00 PM
# re: Asynchronous Fire and Forget With Lambdas
@Haacked - I must have missed this when I was looking through the subtext code.
Requesting Gravatar... JD Conley Jan 14, 2009 10:21 PM
# re: Asynchronous Fire and Forget With Lambdas
@Richard - Thanks for the improvement I just wrote about this nifty little class. It's been hanging out in one of our libraries for too long and needed exposed to the air! ;)

What do you have to say?

(will show your gravatar)
Please add 6 and 8 and type the answer here: