One way, fire and forget action in WCF
Long running process invoke from ASP.NET web application
In case you are building a web app that needs to perform some complex or long running task, for sure you would't want that task to slow down your website or web application.
For example you have a database of growing number of members on your website and you want to send a notification after you published some page on the website. Imagine you have 10,000+ members and that you are composing different message for each of them based on their account information and then sending an email with notification message.
if you do this as a normal action in your MVC controller you will end our by hawing few minutes of waiting in your browser or even worse, you will end up by getting timeout exception from IIS.
There are two things that needs to be done in this case:
1. Separate complex long running task into a different application (running on a separate pool in IIS our outside of IIS or even on a different machine)
2. Return to a web page as soon as possible to avoid request timeout exception
We'll deal with these steps in the following text of this article. Let's first start with separating the complex logic from a controller action to a separate process.
Separate complex long running task into a different application
To make re-factored code more accessible, we'll do it as a WCF service. After implementing the logic, we can host WCF either on same IIS as a invoker web app or host it in windows service, or even on the stand alone machine either in IIS or windows service. This way we have a lot of options to apply depending on our requests and of course budget.
We did not solve the whole problem as it will still take a long time to return to a web application invoker app, but this way we are sure that even if we get an request timeout exception, task will keep running independently from invoker web app until it finishes.
Return to a web page as soon as possible to avoid request timeout exception
Even though we made complex task running in a separate app, we still need to return response to invoker web app, but since complex task will take a lot of time to execute, we will return back to web app only when task is finished.
[ServiceContract] public interface INotificationService { [OperationContract] void SendNotifications(DataContracts.NotificationOptions options); }
Implementation of this interface in a service class:
public class NotificationService : INotificationService { public void SendNotifications(DataContracts.NotificationOptions options) { /* Test long running code start */ for (int i = 0; i < 10; i++) { Thread.Sleep(1000); } /* Test long running code end */ } }
This means we need to make this operation async. The easiest way to make it async is to execute it in a Thread or Task. This way invoker web application will get the response right after this processing task/thread is started.
There is known automation bug in Visual Studio 2012 and Visual Studio 2013 which causes that when you add service reference you do not see it available in C# code.
To resolve this issue, after you add service reference, right click/'Configure Service Reference" and uncheck "Reuse types in reference assemblies"
public class NotificationService : INotificationService { public void SendNotifications(DataContracts.NotificationOptions options) { new Thread(() => { /* Test long running code start */ for (int i = 0; i < 10; i++) { Thread.Sleep(1000); } /* Test long running code end */ }).Start(); } }
One good thing when using this approach is that we can send some value back. For example, if you need to pull out some data from database, let's sayy contact to which you are sending emails, you can send count back and leave email sending hardwork to be done by a separate thread.
public int SendNotifications(DataContracts.NotificationOptions options) { /* get the list of items for a long processing */ var list = Enumerable.Range(1, 10); new Thread(() => { /* Test long running code start */ foreach (int i in list) { Thread.Sleep(1000); } /* Test long running code end */ }).Start(); /* return the number of items that are going to be processed */ return list.Count(); }
Since we want to invoke long running process most likely we will not wait for the response (at least in case of web application), so we can mark long running method as One-way. One-way methods do not return any value and they must be void, otherwise you'll get InvalidOperationException thrown.
System.InvalidOperationException: Operations marked with IsOneWay=true must not declare output parameters, by-reference parameters or return values.
at System.ServiceModel.Description.TypeLoader.CreateOperationDescription(ContractDescription contractDescription, MethodInfo methodInfo, MessageDirection direction, ContractReflectionInfo reflectionInfo, ContractDescription declaringContract)
at System.ServiceModel.Description.TypeLoader.CreateOperationDescriptions(ContractDescription contractDescription, ContractReflectionInfo reflectionInfo, Type contractToGetMethodsFrom, ContractDescription declaringContract, MessageDirection direction)
at System.ServiceModel.Description.TypeLoader.CreateContractDescription(ServiceContractAttribute contractAttr, Type contractType, Type serviceType, ContractReflectionInfo& reflectionInfo, Object serviceImplementation)
at System.ServiceModel.Description.TypeLoader.LoadContractDescriptionHelper(Type contractType, Type serviceType, Object serviceImplementation)
at System.ServiceModel.Description.ContractDescription.GetContract(Type contractType, Type serviceType)
at System.ServiceModel.ServiceHost.CreateDescription(IDictionary`2& implementedContracts)
at System.ServiceModel.ServiceHostBase.InitializeDescription(UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)
at Microsoft.Tools.SvcHost.ServiceHostHelper.CreateServiceHost(Type type, ServiceKind kind)
at Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)
[ServiceContract] public interface INotificationService { [OperationContract(IsOneWay=true)] void SendNotifications(DataContracts.NotificationOptions options); }
When invoking one-way service method from a client you need to invoke async method. Async Task based method will be automatically created when you add service reference to your project
Disadvantage of this approach it that when web application invokes separate long running process, it gets back without any information to for the user avoid the process status, but it is very useful in scenarios when you are sending for example a lot of emails after some action is performed inside application.
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article