Since long time I'm writing custom AsyncCodeActivity classes using the following template:
public sealed class MyActivity : AsyncCodeActivity<T>
{
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var task = new Task<T>(this.Execute, state, CancellationToken.None, TaskCreationOptions.AttachedToParent);
task.ContinueWith(s => callback(s));
task.Start();
return task;
}
protected override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var task = result as Task<T>;
if (task.Exception != null)
{
// Error handling. Rethrow? Cancel?
}
return task.Result;
}
private T Execute(object state)
{
// Logic here
return default(T);
}
}
I have some questions about it:
Thanks
1) You should rethrow the exception from your EndExecute
method.
2) I recommend you create your own base type. I wrote one up called AsyncTaskCodeActivity<T>
below:
public abstract class AsyncTaskCodeActivity<T> : AsyncCodeActivity<T>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var task = ExecuteAsync(context);
var tcs = new TaskCompletionSource<T>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(t.Result);
if (callback != null)
callback(tcs.Task);
});
return tcs.Task;
}
protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var task = (Task<T>)result;
try
{
return task.Result;
}
catch (AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
throw;
}
}
protected abstract Task<T> ExecuteAsync(AsyncCodeActivityContext context);
}
If you use my AsyncEx library, this wrapper becomes much simpler:
public abstract class AsyncTaskCodeActivity<T> : AsyncCodeActivity<T>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var task = ExecuteAsync(context);
return AsyncFactory<T>.ToBegin(task, callback, state);
}
protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
return AsyncFactory<T>.ToEnd(result);
}
protected abstract Task<T> ExecuteAsync(AsyncCodeActivityContext context);
}
Once you have the base type, you can define your own derived type. Here's one that uses async
/await
:
public sealed class MyActivity : AsyncTaskCodeActivity<int>
{
protected override async Task<int> ExecuteAsync(AsyncCodeActivityContext context)
{
await Task.Delay(100);
return 13;
}
}
And here's one that schedules CPU-bound work to the thread pool (similar to your current template):
public sealed class MyCpuActivity : AsyncTaskCodeActivity<int>
{
protected override Task<int> ExecuteAsync(AsyncCodeActivityContext context)
{
return Task.Run(() => 13);
}
}
Update from comments: Here's one that uses cancellation. I'm not 100% sure it's correct, because cancellation is itself asynchronous, and the semantics for AsyncCodeActivity<T>.Cancel
are underdocumented (i.e., Is Cancel
supposed to wait for the activity to complete in a canceled state? Is it acceptable for an activity to complete successfully after Cancel
is called?).
public abstract class AsyncTaskCodeActivity<T> : AsyncCodeActivity<T>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var cts = new CancellationTokenSource();
context.UserState = cts;
var task = ExecuteAsync(context, cts.Token);
return AsyncFactory<T>.ToBegin(task, callback, state);
}
protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
try
{
return AsyncFactory<T>.ToEnd(result);
}
catch (OperationCanceledException)
{
if (context.IsCancellationRequested)
context.MarkCanceled();
else
throw;
return default(T); // or throw?
}
}
protected override void Cancel(AsyncCodeActivityContext context)
{
var cts = (CancellationTokenSource)context.UserState;
cts.Cancel();
}
protected abstract Task<T> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With