【问题标题】:Implementing AsyncCodeActivities (using C# async/await)实现 AsyncCodeActivities(使用 C# async/await)
【发布时间】:2013-06-06 10:55:04
【问题描述】:

长期以来,我一直在使用以下模板编写自定义 AsyncCodeActivity 类:

public sealed class MyActivity : AsyncCodeActivity<T>
{
    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        var task = new Task<T>(this.Execute, state, CancellationToken.None, TaskCreationOptions.AttachedToParent);
        task.ContinueWith(s => callback(s));
        task.Start();
        return task;
    }

    protected override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        var task = result as Task<T>;
        if (task.Exception != null)
        {
            // Error handling. Rethrow? Cancel?
        }

        return task.Result;
    }

    private T Execute(object state)
    {
        // Logic here
        return default(T);
    }
}

我对此有一些疑问:

  1. 处理异常的正确方法是什么?重投?将上下文设置为取消?
  2. 现在有没有一种优雅的方式来使用 async/await 语法编写它?

谢谢

【问题讨论】:

    标签: c# workflow-foundation-4 async-await


    【解决方案1】:

    1) 您应该从 EndExecute 方法中重新抛出异常。

    2) 我建议您创建自己的基本类型。我在下面写了一篇名为AsyncTaskCodeActivity&lt;T&gt; 的文章:

    public abstract class AsyncTaskCodeActivity<T> : AsyncCodeActivity<T>
    {
        protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            var task = ExecuteAsync(context);
            var tcs = new TaskCompletionSource<T>(state);
            task.ContinueWith(t =>
            {
                if (t.IsFaulted)
                    tcs.TrySetException(t.Exception.InnerExceptions);
                else if (t.IsCanceled)
                    tcs.TrySetCanceled();
                else
                    tcs.TrySetResult(t.Result);
    
                if (callback != null)
                    callback(tcs.Task);
            });
    
            return tcs.Task;
        }
    
        protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            var task = (Task<T>)result;
            try
            {
                return task.Result;
            }
            catch (AggregateException ex)
            {
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                throw;
            }
        }
    
        protected abstract Task<T> ExecuteAsync(AsyncCodeActivityContext context);
    }
    

    如果你使用我的 AsyncEx 库,这个包装器会变得更简单:

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

    一旦有了基类型,就可以定义自己的派生类型。这是一个使用async/await的:

    public sealed class MyActivity : AsyncTaskCodeActivity<int>
    {
        protected override async Task<int> ExecuteAsync(AsyncCodeActivityContext context)
        {
            await Task.Delay(100);
            return 13;
        }
    }
    

    这是一个将 CPU 密集型工作安排到线程池的方法(类似于您当前的模板):

    public sealed class MyCpuActivity : AsyncTaskCodeActivity<int>
    {
        protected override Task<int> ExecuteAsync(AsyncCodeActivityContext context)
        {
            return Task.Run(() => 13);
        }
    }
    

    来自 cmets 的更新:这是使用取消的一个。我不是 100% 确定它是正确的,因为取消本身是异步的,并且 AsyncCodeActivity&lt;T&gt;.Cancel 的语义没有充分记录(即,Cancel 是否应该等待活动在取消状态下完成?对于调用Cancel 后活动是否成功完成?)。

    public abstract class AsyncTaskCodeActivity<T> : AsyncCodeActivity<T>
    {
        protected sealed override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            var cts = new CancellationTokenSource();
            context.UserState = cts;
            var task = ExecuteAsync(context, cts.Token);
            return AsyncFactory<T>.ToBegin(task, callback, state);
        }
    
        protected sealed override T EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            try
            {
                return AsyncFactory<T>.ToEnd(result);
            }
            catch (OperationCanceledException)
            {
                if (context.IsCancellationRequested)
                    context.MarkCanceled();
                else
                    throw;
                return default(T); // or throw?
            }
        }
    
        protected override void Cancel(AsyncCodeActivityContext context)
        {
            var cts = (CancellationTokenSource)context.UserState;
            cts.Cancel();
        }
    
        protected abstract Task<T> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken);
    }
    

    【讨论】:

    • 一个小的附加要求:我需要的异步活动应该是选择活动中的一个可能触发器,因此我还需要(优雅)取消......如何扩展它以支持这种情况?谢谢
    • 我不熟悉 WF 取消,但我希望您可以在 BeginExecute 中创建一个 CancellationTokenSource(并将其保存在上下文中),将令牌传递给 ExecuteAsync。然后覆盖Cancel 以从上下文中获取CancellationTokenSource,取消它,然后调用MarkCanceled
    • 感谢更新示例。在我的测试中,它在立即取消除“获胜者”之外的所有分支的选择活动中效果很好
    • 我收到异常“只能在传入的函数范围内访问 ActivityContext。”在执行await 后尝试在ExecuteAsync 方法中使用context
    • @KrisIvanov:我没有看到这个异常,但我也没有大量使用异步活动。我建议您查看代码并确保您没有在任何地方使用async void。如果这不是问题,请发布一个最小的复制作为问题。
    猜你喜欢
    • 2019-09-22
    • 1970-01-01
    • 1970-01-01
    • 2019-03-06
    • 2017-10-10
    • 2016-02-18
    • 2021-11-05
    • 1970-01-01
    • 2021-03-28
    相关资源
    最近更新 更多