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
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);
}
}