What happens when multiple parallel threads expect the same instance of a task, which then throws?

Reading through the answers to this question made me think about what happens with exceptions when the expected task is thrown. Do all "clients" get an exception? I admit that I can mislead a couple of things here; that is the reason I'm asking for clarification.

I will tell you a specific scenario ... Let's say I have a server with a global collection of long Task instances launched by clients. After starting one or more tasks, the client can request their progress and get results when they become available, as well as any errors that may occur.

Tasks themselves can perform very different business-specific things - as a rule, there are no two identical. However, if one of the clients tries to run the same task as the previous one, the server should recognize this and β€œattach” the second client to the existing task instead of wrapping a new copy.

Now, every time when any client requests the status of a task of interest to him, he performs something in this direction:

 var timeout = Task.Delay(numberOfSecondsUntilClientsPatienceRunsOut); try { var result = await Task.WhenAny(timeout, task); if (result == timeout) { // SNIP: No result is available yet. Just report the progress so far. } // SNIP: Report the result. } catch (Exception e) { // SNIP: Report the error. } 

In short, this gives the task some reasonable time to finish what he is doing at the beginning, and then returns to the message of ongoing progress. This means that there is a potentially significant window of time during which several clients can observe the same task.

My question is: if during this window a challenge arises to challenge, is the observed (and handled) exception for all clients?

+5
source share
1 answer

Task.WhenAny will not give up. Per documentation :

The returned task will be completed if any of the tasks are completed. The returned task will always end in a RanToCompletion state with its result set to complete the first task. This is true even if the first task is completed in Canceled or Faulted .

You will return either timeout or task .

If the result is task , and you expect (or get Task.Result ), and task will work, then it will be thrown. It doesn't matter how many callers do it or when they do it - an attempt to get the result of a faulty task always throws. Simple code demonstrating this:

 var t = Task.Run(() => throw new Exception(DateTime.Now.Ticks.ToString())); try { await t; } catch (Exception e) { Console.WriteLine(e.Message); } await Task.Delay(1000); try { await t; } catch (Exception e) { Console.WriteLine(e.Message); } 

This will print the same timestamp twice. A task runs only once and has only one result, which throws an exception every time you try to get it. If you like, you can mix in different threads or in parallel calls, this does not change the result.

Please note that in the case of a timeout, the main possibility of the race condition still exists: two different tasks / threads waiting for the same task can get different results on await Task.WhenAny(timeout, task) , based on the task that they observe first complete. In other words, even if await Task.WhenAny(timeout, task) == timeout , the task can still be damaged at any point between .WhenAny() , deciding that it was done, and control ultimately comes back to you. This is to be expected, and your code should handle it (in the next round of waiting .WhenAny() will immediately return with a failed task).

+4
source

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


All Articles