使用成功和故障处理程序在 csharp 中链接任务

Chaining Tasks in csharp with success and fault handler

编辑 请参阅我问题末尾的标题 "Problem" 以破解此问题。

来自我们可以链接承诺的 nodejs,在 C# 中,我看到异步任务几乎具有可比性。这是我的尝试。

编辑 - 我无法将超级级别的调用方方法标记为 async,因为基于 dll 的库正在调用它

来电object

public void DoSomething(MyRequest request) 
{
    Delegate.Job1(request)
        .ContinueWith(Delegate.Job2)
        .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
        .ContinueWith(Result);
}

public void Result(Task<MyRequest> task)
{
    MyRequest request = task.Result;
    Console.Writeline(request.result1 + " " + request.result2);
}

public void Fault(Task<MyRequest> task)
{
   MyRequest request = task.Result;
   Console.Writeline(request.result);
}

代表Object

public async Task<MyRequest> Job1(MyRequest request) 
{
    var data = await remoteService.Service1Async();
    request.result1 = data;
    return request;
}

public async Task<MyRequest> Job2(Task<MyRequest> task)
{
    var data = await remoteService.Service2Async();
    request.result2 = data;
    return request;
}

问题

1) 编辑(已修复,链接到我的项目的 dll 丢失了它的链接 dll)Task.Result(请求)在 Result 方法中为 null,另外 Status = Faulted

2) 故障处理是否正确?我希望只有在委托方法中发生异常时才会调用 Fault,否则应该跳过。

2-b) 另一种选择是在 Result 函数中检查(删除 Fault 函数)如果 Task.status = RanTocompletion 并在那里分支以判断成功或错误

回答后编辑

我有一个问题,如果我不能让我的控制器异步怎么办。

控制器

public void ICannotBeAsync()
{
    try
    {
        var r = await caller.DoSomething(request); // though I can use ContinueWith here, ???
    }
    catch(Exception e)
    {
        //exception handling
    }
}

来电

public async Task DoSomethingAsync(MyRequest request)
{
     request.result1 = await delegateInstance.Job1(request);
     request.result2 = await delegateInstance.Job2(request);
     Console.Writeline(request.result1 + " " + request.result2);
     return result;
}

编辑 2 - 基于 VMAtm 编辑,请查看 OnlyOnFaulted(故障)选项。

Delegate.Job1(request)
    .ContinueWith(_ => Delegate.Job2(request), TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(() => {request.result = Task.Exception; Fault(request);}, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion);

问题 -

测试一下,下面的实际代码,ResultFault 的 none 被调用,尽管方法 GetCustomersAsync 返回成功。我的理解是一切都在 Fault 处停止,因为它仅在 Fault 上被标记为 运行,执行在那里停止并且未调用 Result 处理程序。

Customer.GetCustomersAsync(request)
    .ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

编辑 3 基于 Evk 的回答。

Task<Request> task = Customer.GetCustomersAsync(request);
task.ContinueWith(_ => Job2Async(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(_ => Job3Async(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(_ => Result(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(t => { request.Result = t.Exception; Fault(request); }, TaskContinuationOptions.OnlyOnFaulted);

此代码存在多个问题:

Delegate.Job1(request)
    .ContinueWith(Delegate.Job2)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result);

首先,即使 Delegate.Job1 失败,您仍将继续执行 Delegate.Job2。所以这里需要一个 OnlyOnRanToCompletion 值。与 Result 延续类似,您在所有情况下都在继续,因此出现错误的任务仍会通过链,并且如您所见,处于 Faulted 状态 null 结果。

因此,如果您不能在该级别 await 上使用,您的代码可能是这样的(另外,正如@Evk 所述,您必须将异常处理添加到所有代码中,这真的很难看):

Delegate.Job1(request)
    .ContinueWith(Delegate.Job2, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted);

但是,您仍然可以选择在您的方法中使用 await 关键字,然后使用 lambda 同步地 运行 它,如果您可以选择:

public async Task DoSomethingAsync(MyRequest request)
{
    try
    {
         request.result1 = await delegateInstance.Job1(request);
         request.result2 = await delegateInstance.Job2(request);
         Console.Writeline(request.result1 + " " + request.result2);
         return result;
     }
     catch(Exception e)
     {

     }
}

public void ICannotBeAsync()
{
    var task = Task.Run(() => caller.DoSomethingAsync(request);
    // calling the .Result property will block current thread
    Console.WriteLine(task.Result);
}

异常处理可以在任一级别上完成,因此由您决定在何处引入它。如果在执行过程中出现问题,Result property will raise an AggregateException as a wrapper to inner exceptions happened during the call. Also you can use a Wait method for a task, wrapped into a try/catch clause, and check the task state after that, and deal with it as you need (it has IsFaulted, IsCompleted, IsCanceled 布尔属性)。

此外,强烈建议为您的 task-oriented 任务使用一些取消逻辑,以便能够取消不必要的工作。您可以从 this MSDN article 开始。

根据您的其他问题更新:

如果您仍然想使用 ContinueWith 而不是 await,并且想更改 Job1Job2 方法的签名,您应该更改你的代码是这样的:

Delegate.Job1(request)
    .ContinueWith(_ => Delegate.Job2(request), TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted);

原因是 ContinueWith 方法接受 Func<Task, Task>,因为一般来说,您需要检查任务的状态 and/or 它是结果。

关于不屏蔽来电的问题,你可以试试TaskCompletionSource<TResult>class,像这样:

public void ICannotBeAsync()
{
    var source = new TaskCompletionSource<TResult>();
    var task = Task.Run(() => caller.DoSomethingAsync(request, source);
    while (!source.IsCompleted && !source.IsFaulted)
    {
        // yeild the execution to other threads for now, while the result isn't available
        Thread.Yeild();
    }
}

public async Task DoSomethingAsync(MyRequest request, TaskCompletionSource<TResult> source)
{
     request.result1 = await delegateInstance.Job1(request);
     request.result2 = await delegateInstance.Job2(request);
     Console.Writeline(request.result1 + " " + request.result2);
     source.SetResult(result);
}

这里说了很多,所以我只回答最后"Problem"部分:

Customer.GetCustomersAsync(request)
    .ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

这里的问题(在原始示例中也是如此)是发生以下情况:

  1. 您继续 GetCustomersAsync 并继续 "only on faulted"。
  2. 然后你继续那个继续,而不是GetCustomersAsync,下一个继续,它只能在完成时运行。

结果,当GetCustomersAsync 失败时,两个continations 只能执行。修复:

var request = Customer.GetCustomersAsync(request);
request.ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted);
request.ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

请注意,即使您不能更改某些方法的签名并且它绝对必须 return 无效,您仍然可以将其标记为异步:

public async void DoSomethingAsync(MyRequest request)
{
     try {
         await Customer.GetCustomersAsync(request);
         Result(request);      
     }
     catch (Exception ex) {
         Fault(request);
     }
}