0

I am creating a number of tasks in a loop like so and adding them to a List.

for (int startThreads = 0; startThreads < beginThreadsPerDS; startThreads++)
{
    var extDbCnn_threaded = new SqlConnection(extDbConnectionString);
        var srcDbCnn_threaded = new SqlConnection(srcDbConnectionString);
                        
        var queryTasks = Task.Run(() => ExtractDataSetCode_Query(extDbCnn_threaded, srcDbCnn_threaded, strTmpDsc, strPatList, row, tbOutputPath.Text, strReportScope, strOutFormat, strDataSetType, loggingEnabled, /*dataSetNum,*/ lstDataSetCdTasks));

        lock (LstDataSetCdTasks)
        {
            lstDataSetCdTasks.SetQueryTasksForDataSetCode(strTmpDsc, queryTasks);
        }
}

I then create a task that completes when all of the above complete:

var queryWaitTask = Task.WhenAll(lstDataSetCdTasks.GetQueryTasksByDataSetCode(strTmpDsc));

So that I can use that task to add the continuation task below, which should only run once all the "Query Tasks" have completed:

var queryReassignmentTask = queryWaitTask.ContinueWith(antecedent => ExtractDataSetCode_assignTasksToNextDS(extDbCnn, srcDbCnn, strTmpDsc, strPatList, row, tbOutputPath.Text, strReportScope, strOutFormat, strDataSetType, loggingEnabled, lstDataSetCdTasks), TaskContinuationOptions.OnlyOnRanToCompletion);

My problem is that at times, in the "ExtractDataSetCode_Query" method (signature shown below) that is called by each of the original tasks I spawn, I get an error thrown from one of the stored procedures that is called in that method. When that happens, I want to be able to handle it and notify the user that an error occurred in one of the tasks (as opposed to what happens now is that the faulted task just exits/stops running the ExtractDataSetCode_Query method while the others keep going. I am only made aware that anything has happended once the last Task completes (which can be hours or days later). I want to know that a task has exited/faulted when it happens. Everything I am finding about how to handle these situations mentions creating a continuation task that continues "OnlyOnFaulted", however, given how I am spawning all these tasks then storing them in a List and THEN waiting on them based on the Task.WhenAll, I just dont see how that can work. What is the best way to accomplish this? Thanks!

private void ExtractDataSetCode_Query(SqlConnection extDbCnn, SqlConnection srcDbCnn, String extDataSetCode, String patList, DataGridViewRow currRow, String strOutputPath, String strRptScope, String strOutFmt, String strDataSetTyp, String strLogEnable, LstDataSetCdTasks lstDataSetCdTasks)
``
adveach
  • 55
  • 1
  • 8
  • Can you make the code in each task (or a continuation of each task) to invoke an event or a callback when an error occurs? – StriplingWarrior Aug 15 '23 at 20:29
  • Does this answer your question? [Is it possible to get successful results from a Task.WhenAll when one of the tasks fails?](https://stackoverflow.com/questions/55887028/is-it-possible-to-get-successful-results-from-a-task-whenall-when-one-of-the-tas) -or- [Ignore the Tasks throwing Exceptions at Task.WhenAll and get only the completed results](https://stackoverflow.com/q/39589640/1220550) – Peter B Aug 15 '23 at 20:32
  • 1
    Wrap your Task.WhenAll with try/catch. In catch assemble those tasks where Task.Exception is not null. That's it. – Ryan Aug 15 '23 at 20:32
  • That list of tasks can be lazy ienumerable too, so you can control startup timing, btw. – Ryan Aug 15 '23 at 20:40
  • @PeterB Those are good posts, but as far as I can tell, those would be too late for my needs. Meaning I need to know as soon as one of the tasks fails/exits/has an error thrown by the method its calling. By the time Task.WhenAll executes, the last task has completed. I was thinking I could do something like: `var queryWaitTaskFirst = Task.WhenAny(lstDataSetCdTasks.GetQueryTasksByDataSetCode(strTmpDsc)); var errorTask = queryWaitTaskFirst.ContinueWith(t => MessageBox.Show(t.Exception.Message), TaskContinuationOptions.NotOnRanToCompletion); ` But this didnt work either.. – adveach Aug 15 '23 at 20:49
  • I suggest you modify `ExtractDataSetCode_Query` so that it handles the exception rather than letting it bubble out of the task. One of the ways it could be handled is to fire an event, which you can handle by displaying the desired error message. (If you can't modify the method, you can wrap it in a lambda that fires the event instead). – John Wu Aug 15 '23 at 21:00
  • Basically, it seems like I would need a Task.WhenAny() type of method that returns a task When Any task in the list/array passed to it faults/errors/exits or otherwise doesnt run to completion successfully. Right? – adveach Aug 15 '23 at 21:00
  • 1
    Okay now I see. [This answer](https://stackoverflow.com/a/60482164/1220550) offers a method **`WhenAllFailFast`** that might do what you need. – Peter B Aug 15 '23 at 21:28
  • @PeterB. Yes that looks like it will work. Thanks! – adveach Aug 16 '23 at 14:47
  • Does this answer your question? [How can I await an array of tasks and stop waiting on first exception?](https://stackoverflow.com/questions/57313252/how-can-i-await-an-array-of-tasks-and-stop-waiting-on-first-exception) – TylerH Aug 16 '23 at 15:21

1 Answers1

1

Handle the exception in the task. It's way easier that way.

var queryTasks = Task.Run(() => {
    try
    {
        ExtractDataSetCode_Query(extDbCnn_threaded, srcDbCnn_threaded, strTmpDsc, strPatList, row, tbOutputPath.Text, strReportScope, strOutFormat, strDataSetType, loggingEnabled, /*dataSetNum,*/ lstDataSetCdTasks));
    }
    catch (SqlException exception)
    {
        //Notify user of error here
    }
});
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • I actually tried this, and it didnt work. I added a MessageBox in the catch block to reiterate the Exception message. It didnt show when there were errors thrown in the method. – adveach Aug 15 '23 at 21:25
  • If u are not sure about specific exception u can use just Exception type in a catch clause. – Ryan Aug 15 '23 at 21:36
  • 1
    I suggest you troubleshoot the problem until you can get it to work rather than abandoning the design. There are [reasons](https://stackoverflow.com/questions/12949333/message-box-from-another-thread) why it is tricky to use a `MessageBox` from another thread, so I suggest using an event, using `Invoke`, or even just setting a flag and letting the main thread handle it. Also, make sure you are catching the right kind of exceptions (^this is just an example). – John Wu Aug 15 '23 at 21:42
  • I did try multiple different ways to get this to work including using System.Diagnostics.Debug.WriteLine in the catch. No message was displayed at the time that the error was thrown from the stored procedure in the ExtractDataSetCode_Query() method. – adveach Aug 16 '23 at 13:41
  • I also tried changing the catch to the generic (Exception ex). I still got nothing firing at the time that the exception was thrown. I can get it to work by using TRY\CATCH inside the ExtractDataSetCode_Query() method using the exact same code as above, however, that really isnt a great solution here for several other reasons. – adveach Aug 16 '23 at 14:16
  • 1
    @JohnWu Ok. Im an idiot. This works. I put the code in the wrong place. There are two sections of the code that create/spawn these tasks. The one I put it in isnt the one that was actually creating the tasks in my specific case that I was testing! Duh. Sorry. Your solution works! – adveach Aug 16 '23 at 14:57