Skip to content

Commit

Permalink
Add AsyncTaskCodeActivity (#149)
Browse files Browse the repository at this point in the history
* Add AsyncTaskCodeActivity and VoidResult

* Added tests for AsyncTaskCodeActivity, removed ifdef for .NET 5 or older

* Add VoidResultTests

* Revert TerminateWorkflowTests changes, Remove IAsyncTaskCodeActivity, Remove VoidResult in favor of object

* Revert Assembly Attribute, Add non-generic test

* Remove comment about VoidResult type

* add Yield

* code reuse through a base class

* cosmetic

* error case

* use ApmAsyncFactory.ToBegin

* cancellation test

Co-authored-by: Lucian Bargaoanu <[email protected]>
  • Loading branch information
Foxtrek64 and lbargaoanu authored Jun 7, 2021
1 parent d675597 commit 411c9e2
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/CoreWf/AsyncTaskCodeActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Nito.AsyncEx.Interop;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
namespace System.Activities
{
public abstract class AsyncTaskCodeActivity : TaskCodeActivity<object>
{
public abstract Task ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
internal sealed override async Task<object> ExecuteAsyncCore(AsyncCodeActivityContext context, CancellationToken cancellationToken)
{
await ExecuteAsync(context, cancellationToken);
return null;
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public new OutArgument<object> Result { get; }
}
public abstract class AsyncTaskCodeActivity<TResult> : TaskCodeActivity<TResult>
{
public abstract Task<TResult> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
internal sealed override Task<TResult> ExecuteAsyncCore(AsyncCodeActivityContext context, CancellationToken cancellationToken) =>
ExecuteAsync(context, cancellationToken);
}
public abstract class TaskCodeActivity<TResult> : AsyncCodeActivity<TResult>
{
protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var cts = new CancellationTokenSource();
context.UserState = cts;

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

return ApmAsyncFactory.ToBegin(task, callback, state);
}
protected sealed override TResult EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
using ((CancellationTokenSource)context.UserState)
{
return ((Task<TResult>)result).Result;
}
}
protected sealed override void Cancel(AsyncCodeActivityContext context) => ((CancellationTokenSource)context.UserState).Cancel();
internal abstract Task<TResult> ExecuteAsyncCore(AsyncCodeActivityContext context, CancellationToken cancellationToken);
}
}
1 change: 1 addition & 0 deletions src/CoreWf/System.Activities.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.0" />
</ItemGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
Expand Down
95 changes: 95 additions & 0 deletions src/Test/TestCases.Activities/AsyncTaskCodeActivityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Shouldly;
using System;
using System.Activities;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace TestCases.Activities
{
public sealed class AsyncTaskCodeActivityTests
{
[Fact]
public void ShouldReturnVoidResult()
{
var genericActivity = new AsyncTaskActivity<object>(_=>Task.FromResult<object>(null));

object vr1 = null;
object vr2 = WorkflowInvoker.Invoke<object>(genericActivity);

Assert.Equal(vr1, vr2);
}
[Fact]
public void ShouldThrow()
{
Activity activity = new AsyncTaskActivity(_ => Task.FromException(new InvalidOperationException("@")));
new Action(() => WorkflowInvoker.Invoke(activity)).ShouldThrow<InvalidOperationException>().Message.ShouldBe("@");
}
[Fact]
public async Task ShouldCancel()
{
Activity activity = new AsyncTaskActivity(token=> Task.Delay(Timeout.Infinite, token));
var invoker = new WorkflowInvoker(activity);
var taskCompletionSource = new TaskCompletionSource();
InvokeCompletedEventArgs args = null;
invoker.InvokeCompleted += (sender, localArgs) =>
{
args = localArgs;
taskCompletionSource.SetResult();
};
invoker.InvokeAsync(invoker);
invoker.CancelAsync(invoker);
await taskCompletionSource.Task;
args.Error.ShouldBeOfType<WorkflowApplicationAbortedException>();
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void ShouldReturnConstantResult(int value)
{
var activity = new AsyncTaskActivity<int>(async _=>
{
await Task.Yield();
return value;
});
var result = WorkflowInvoker.Invoke(activity);

Assert.Equal(value, result);
}
[Fact]
public void ShouldWriteCorrectString()
{
const string stringToWrite = "Hello, World!";

using var memory = new MemoryStream();

Activity activity = new AsyncTaskActivity(async _ =>
{
using var writer = new StreamWriter(memory);
writer.Write(stringToWrite);
writer.Flush();
});

_ = WorkflowInvoker.Invoke(activity);

byte[] buffer = memory.ToArray();

Assert.Equal(stringToWrite, Encoding.UTF8.GetString(buffer, 0, buffer.Length));
}
}
public class AsyncTaskActivity : AsyncTaskCodeActivity
{
private readonly Func<CancellationToken, Task> _action;
public AsyncTaskActivity(Func<CancellationToken, Task> action) => _action = action;
public override Task ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken) => _action(cancellationToken);
}
public class AsyncTaskActivity<TResult> : AsyncTaskCodeActivity<TResult>
{
private readonly Func<CancellationToken, Task<TResult>> _action;
public AsyncTaskActivity(Func<CancellationToken, Task<TResult>> action) => _action = action;
public override Task<TResult> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken) => _action(cancellationToken);
}
}

0 comments on commit 411c9e2

Please sign in to comment.