Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AsyncTaskCodeActivity #149

Merged
merged 13 commits into from
Jun 7, 2021
154 changes: 154 additions & 0 deletions src/CoreWf/AsyncTaskCodeActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System.Threading;
using System.Threading.Tasks;

namespace System.Activities
{
public abstract class AsyncTaskCodeActivity : AsyncCodeActivity
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var cts = new CancellationTokenSource();
context.UserState = cts;

var task = ExecuteAsync(context, cts.Token);

// VoidResult allows us to simulate a void return type.
var tcs = new TaskCompletionSource<object>(state);

task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception!.InnerExceptions); // If it's faulted, there was an exception.
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
_ = tcs.TrySetResult(null);
}

callback?.Invoke(tcs.Task);
}, cts.Token);

return tcs.Task;
}

protected sealed override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var task = (Task) result;
try
{
task.Wait();
}
catch (TaskCanceledException)
{
if (context.IsCancellationRequested)
{
context.MarkCanceled();
}

throw;
}
catch (AggregateException aex)
{
foreach (var ex in aex.Flatten().InnerExceptions)
{
if (ex is not TaskCanceledException)
return;

if (context.IsCancellationRequested)
context.MarkCanceled();
}

throw;
}
}

protected sealed override void Cancel(AsyncCodeActivityContext context)
{
var cts = (CancellationTokenSource)context.UserState;
cts.Cancel();
}

public abstract Task ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}

public abstract class AsyncTaskCodeActivity<TResult> : AsyncCodeActivity<TResult>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var cts = new CancellationTokenSource();
context.UserState = cts;
var task = ExecuteAsync(context, cts.Token);
var tcs = new TaskCompletionSource<TResult>(state);

task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception!.InnerExceptions); // If it's faulted, there was an exception.
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(t.Result);
}

callback?.Invoke(tcs.Task);
}, cts.Token);

return tcs.Task;
}

protected sealed override TResult EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var task = (Task<TResult>) result;
try
{
if (!task.IsCompleted)
{
task.Wait();
}
return task.Result;
}
catch (OperationCanceledException)
{
if (context.IsCancellationRequested)
{
context.MarkCanceled();
}

throw;
}
catch (AggregateException aex)
{
foreach (var ex in aex.Flatten().InnerExceptions)
{
if (ex is not OperationCanceledException)
continue;

if (context.IsCancellationRequested)
{
context.MarkCanceled();
}
}

throw;
}
}

protected sealed override void Cancel(AsyncCodeActivityContext context)
{
var cts = (CancellationTokenSource) context.UserState;
cts.Cancel();
}

public abstract Task<TResult> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}
}
3 changes: 3 additions & 0 deletions src/CoreWf/Attributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("TestCases.Activities")]
Foxtrek64 marked this conversation as resolved.
Show resolved Hide resolved
42 changes: 42 additions & 0 deletions src/Test/TestCases.Activities/AsyncTaskCodeActivityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Activities;
using System.Threading.Tasks;
using TestObjects.CustomActivities;
using Xunit;

namespace TestCases.Activities
{
public sealed class AsyncTaskCodeActivityTests
{
private static readonly AsyncTaskActivity<object> genericActivity = new(Task.FromResult<object>(null));
Foxtrek64 marked this conversation as resolved.
Show resolved Hide resolved

[Fact]
public void ShouldReturnVoidResult()
{
object vr1 = null;
object vr2 = WorkflowInvoker.Invoke<object>(genericActivity);

Assert.Equal(vr1, vr2);
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void ShouldReturnConstantResult(int value)
{
var activity = new AsyncTaskActivity<int>(Task.FromResult(value));
var result = WorkflowInvoker.Invoke(activity);

Assert.Equal(value, result);
}

[Fact]
public void ShouldReturnCalculatedValue()
{
var activity = new AsyncTaskActivity<int>(() => 5 + 5);
var result = WorkflowInvoker.Invoke(activity);

Assert.Equal(10, result);
}
}
}
23 changes: 23 additions & 0 deletions src/Test/TestObjects/CustomActivities/AsyncTaskActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Activities;
using System.Threading;
using System.Threading.Tasks;

namespace TestObjects.CustomActivities
{
public class AsyncTaskActivity<TResult> : AsyncTaskCodeActivity<TResult>
{
private readonly Task<TResult> _task;
public AsyncTaskActivity(Task<TResult> task)
{
_task = task;
}

public AsyncTaskActivity(Func<TResult> func) : this(Task.Run(func))
{
}

public override Task<TResult> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken)
=> _task;
}
}