Suppose I have a service which will be doing a long, expensive synchronous operation, i.e.
class ExclamationMarkService
{
public string GetData(string param)
{
Thread.Sleep(5000);
return param + "!";
}
}
To wrap it to become asynchronous via the EAP pattern I can do this:
class ExclamationMarkServiceEAP
{
public delegate void GetDataHandler(string data);
public event GetDataHandler GetDataCompleted;
public void GetDataWorker(object param)
{
var service = new ExclamationMarkService();
string data = service.GetData((string)param);
if (GetDataCompleted != null)
{
GetDataCompleted(data);
}
}
public void GetData(string param)
{
var thread = new Thread(GetDataWorker) {IsBackground = true};
thread.Start(param);
}
}
A similar thing with the new async/await operators can be done this way:
class ExclamationMarkServiceTaskAsync
{
public async Task<string> GetDataAsync(string param)
{
var service = new ExclamationMarkService();
return await Task.Run(() => service.GetData(param));
}
}
Usage:
public static void CallExclamationMarkServiceEAP()
{
var service = new ExclamationMarkServiceEAP();
service.GetDataCompleted += service_GetDataCompleted;
service.GetData("hello EAP");
}
static void service_GetDataCompleted(string data)
{
Console.WriteLine(data);
}
public static async void CallExclamationMarkServiceTaskAsync()
{
var service = new ExclamationMarkServiceTaskAsync();
var data = await service.GetDataAsync("hello TaskAsync");
Console.WriteLine(data);
}
static void Main(string[] args)
{
CallExclamationMarkServiceEAP();
CallExclamationMarkServiceTaskAsync();
Console.Read();
}
In both cases I managed to offload the work to the background. In the case of EAP, by explicitly starting a thread. For the async/await version, by using Task.Run.
Questions:
- How would an APM implementation of my ExclamationMarkService look like?
- Given both EAP and APM versions, how can they be wrapped by using existing methods of the Task class (Task.Factory.StartNew / Task.Factory.FromAsync, etc.) so that they could be used with the async/await keywords.
Long running synchronous operations that are not IO bound do not belong in the ThreadPool. Running such operations in the ThreadPool exposes you to the risk of starvation, where the pool does not spin up threads fast enough to be responsive to the needs of the many other APIs that rely on it.
If you want to run something long-winded, run it on your own thread, being careful to marshall the result back to the right context if it needs to show in some UI.
As such your first approach seems more appropriate.
On the other hand, TPL offers the opportunity to hint that the task is long running, and allows the system to decide the best place to run it. It’s as simple as:
StartNewreturns a Task. You can await it.