UiPath/CoreWF

Add AsyncTaskCodeActivity type

Closed this issue · 2 comments

Currently for those wishing to build a custom asynchronous activity, one must implement AsyncCodeActivity and work with the NetFx 3 async library, implementing BeginExecute and EndExecute. For many newer developers, this model is foreign and may be difficult to find adequate documentation of how to implement an activity using this model.

I would like to propose a new type, AsyncTaskCodeActivity, which instead exposes an abstract ExecuteAsync() method which returns a Task or Task<T> as appropriate. This can reuse much of the existing code available in the Windows Workflow Foundation library, such as AsyncCodeActivityContext.

Proposed API Changes

public abstract class AsyncTaskCodeActivity : AsyncCodeActivity
{
    protected abstract Task ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}
public abstract class AsyncTaskCodeActivity<TValue> : AsyncCodeActivity<TValue>
{
    protected abstract Task<TValue> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}

Considerations/changes:

  1. Should System.ValueTask be used instead of System.Task? If a user wraps a MemoryStream or some other existing ValueTask that can return synchronously, ValueTask allows us to wrap the return value and avoid allocating. There are however a couple of potential downsides:
    • ValueTask/ValueTask<T> is less usable than its Task/Task<T> counterpart. However, since ValueTask/ValueTask<T> is best used in environments where it is simply going to be awaited, this makes it a particularly good candidate for UiPath. If it is being called outside of the UiPath code runner, then it is likely being done in a unit test by the developer who is building the activity. It will be on them to handle it appropriately.
    • ValueTask/ValueTask<T> is larger than Task/Task<T> when wrapping Task/Task<T>. This is a measurable difference in microbenchmarks, however for utilization in an environment like UiPath, I doubt it will make a big difference.
  2. Is AsyncCodeActivity the correct target for extension? Should it instead extend Activity like AsyncCodeActivity does? The way my current implementation of AsyncTaskCodeActivity works, uses BeginExecute to start the task and uses a TaskCompletionSource to track the lifetime of the task. Then it uses EndExecute to return the value of the task. In the case of AsyncCodeActivity (without a return type), it uses an empty VoidResult struct as its result (since I can't use void as a return type). This type works similarly to MediatR's Unit type.

Should there be an interest in this change, I would be happy to submit a PR.

Sounds good! A PR is welcome.

  1. We're not a high performance library, so Task seems best.
  2. It seems there's useful stuff to reuse in AsyncCodeActivity. But make sure to seal BeginExecute/EndExecute. Also see this.

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.