Chain tasks in csharp with success and error handler

Edit See the heading “Problem” at the end of my question to crack this question.

Starting with nodejs, where we could chain promises, in C # I can see that Async Tasks is almost comparable. Here is my attempt.

Edit - I cannot mark my uber level invocation methods as async , since the dll-based library calls it

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

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

Problem :

1) Edit (fixed, the associated DLL with my project was missing an associated dll) Task.Result (request) comes as null in the Result method, also Status = Faulted

2) Also fixed a bug? I expect Fault to be called only when an exception occurs in the delegate methods, otherwise it should skip.

2-b) Another option is checking inside the Result function (delete Fault function) if Task.status = RanTocompletion and debugging there for success or error

Edit after reply

I have a question what to do if I cannot configure my asynchronous controller.

controller

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

Caller

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

Change 2 - Based on VMAtm Edit, view the OnlyOnFaulted (Fault) option.

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

The problem is

Give it the test actual code below, none of the Result or Fault is called, although the GetCustomersAsync method returned successfully. My understanding of everything stops at Fault , because it is marked to run only at Fault , execution stops, and the Result handler Result not called.

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

Edit 3 Configure Evk's response.

 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); 
+1
source share
2 answers

Much has been said here, so I will only answer the last section, “Problem”:

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

The problem here (and in the original example too) is this:

  • Continuation of GetCustomersAsync with the continuation of "only in case of failure."
  • Then you continue this continuation , not GetCustomersAsync , with the next continuation, which can only be executed upon completion.

As a result, both events can only execute when GetCustomersAsync fails. To fix:

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

Please note that even if you cannot change the signature of a method, and it absolutely must return void, you can still mark it as async:

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

There are several problems with this code:

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

First of all, you continue to execute using Delegate.Job2 , even if Delegate.Job1 fails. Therefore you need the value OnlyOnRanToCompletion . Like continuing the Result , you continue in all cases, so the task with the error still goes through the chain and, as you have already seen, is in the Faulted state with null as a result.

So, your code, if you cannot use await at this level, may be like this (just as @Evk pointed out, you had to add exception handling to all your code, which is really ugly):

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

However, you still have the option to use the await keyword inside your method, and after that use lambda to run it synchronously, if this is an option for you:

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

Exception handling can be performed at all levels, so you need to submit it. If something goes wrong at runtime, Result will raise an AggregateException as a wrapper for internal exceptions that occurred during the call. You can also use the Wait method for a task wrapped in a try/catch clause, and then check the status of the task and process it as necessary (it has IsFaulted , IsCompleted , IsCanceled boolean properties).

In addition, it is strongly recommended that you use some undo logic for task-oriented tasks to undo unnecessary work. You can start with this MSDN article .

Update on other issues:

If you still want to use ContinueWith instead of await and want to change the method signatures of Job1 , Job2 , you must change your code as follows:

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

The reason for this is that the ContinueWith method accepts Func<Task, Task> , because you need to, in general, check the task for status and / or result.

Regarding the issue of not blocking the caller, you can try the TaskCompletionSource<TResult> class, something like this:

 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); } 
+1
source

Source: https://habr.com/ru/post/1263842/


All Articles