Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AsyncTaskCodeActivity and lost context after await

AsyncTaskCodeActivity fails when the context parameter is accessed after the first await is performed. For example:

public class TestAsyncTaskCodeActivity : AsyncTaskCodeActivity<int>
{
    protected async override Task<int> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken)
    {
        await Task.Delay(50);
        // context has already been disposed and the next line throws
        // ObjectDisposedException with the message:
        // An ActivityContext can only be accessed within the scope of the function it was passed into.
        context.Track(new CustomTrackingRecord("test"));
        // more awaits can happen here
        return 3;
    }
}

Is there any simple way to preserve the context so it can be used also after awaiting something?

like image 744
arturek Avatar asked Sep 26 '14 07:09

arturek


1 Answers

Ah.

When I wrote AsyncTaskCodeActivity<T>, I assumed that the AsyncCodeActivityContext was in fact going to be the same instance at the beginning and end of the asynchronous method, and be available all the way through. This is not the case (which is a bit odd - not sure why the WF team made that decision).

Instead, the AsyncCodeActivityContext can only be accessed at the beginning and end of the activity. Awkward, indeed.

The updated code below will allow you to access the context at the beginning (e.g., reading In variables) and then access the context again at the end. I also introduce an optional TState, which can be used for storing activity state (which the activity can access throughout its execution). Let me know if this fits your needs; I haven't tested it.

public abstract class AsyncTaskCodeActivity<T, TState> : AsyncCodeActivity<T>
{
  protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
  {
    TState activityState = PreExecute(context);
    context.UserState = activityState;
    var task = ExecuteAsync(activityState);
    return AsyncFactory<T>.ToBegin(task, callback, state);
  }

  protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult asyncResult)
  {
    var result = AsyncFactory<T>.ToEnd(asyncResult);
    return PostExecute(context, (TState)context.UserState, result);
  }

  protected virtual TState PreExecute(AsyncCodeActivityContext context)
  {
    return default(TState);
  }
  protected abstract Task<T> ExecuteAsync(TState activityState);
  protected virtual T PostExecute(AsyncCodeActivityContext context, TState activityState, T result)
  {
    return result;
  }
}
public abstract class AsyncTaskCodeActivity<T> : AsyncTaskCodeActivity<T, object>
{
}
like image 150
Stephen Cleary Avatar answered Sep 28 '22 06:09

Stephen Cleary