How to create one-to-one multithreading relationship and handling exception in C#

75 views Asked by At

It's the first time that I work on multithreading application (in general, not only with C#), and for this reason I know only simple notions about it. I've realized basically implementation with System.Threading namespace.

I've a dictionary which link objects A with its ID (Dictionary<int,A>). Every A has to do an operation (the same operation for every A), and I would like that this operations work in a list of separate thread, one for each A.

I know that System.Threading is bad way to realize multithreading, but I know a little bit only this namespace at this moment. I made something as:


public class ThreadManager
{
    private Dictionary<int, A> _aList;
    public Dictionary<int, A> AList
    {
        get { return _aList; }
        set { _aList = value; }
    }

    private Dictionary<int, Thread> _threadList;
    public Dictionary<int, Thread> ThreadList
    {
        get { return _threadList; }
        set { _threadList = value; }
    }

    public ThreadManager(Dictionary<int, A> list)
    {
        AList = list;
        ThreadList = new Dictionary<int, Thread>();

        foreach (A a in AList)
        {
            int id = a.Id;
            ThreadList.Add(id, new Thread(new ParameterizedThreadStart(Operation)));
        }
    }

    public void Operation(object data)
    {
        try
        {
            /*** do something***/
        }
        catch (Exception e)
        {
            /*** throw exception ***/
        }
    }
}

When I run specific thread in out point of my application code with:

ThreadManager.ThreadList[i].Start(data)
 

it seems work fine to do needed operation. However, I think that this solution is really ugly and not very functional.

Furthermore, there is another big problem. When exception in throw, as expected, it isn't catch out of thread. Can I catch thread exception in another point of my code, which work on different thread (for example the main thread)?

Edit:

In order to be clear, I would like that no threads blocking main thread. I need to be able to operate on the main thread while operations work.

1

There are 1 answers

7
Theodor Zoulias On

My suggestion it to use a combination of async/await, Task.Run and Parallel.ForEach:

private async void button1_Click(object sender, EventArgs e)
{
    try
    {
        Cursor = Cursors.WaitCursor;
        button1.Enabled = false;

        await Task.Run(() =>
        {
            ParallelOptions options = new()
            {
                MaxDegreeOfParallelism = Environment.ProcessorCount
            };

            Parallel.ForEach(dictionary, options, entry =>
            {
                var (key, value) = entry;
                /* do something with the key and value */
            });
        });
    }
    catch (AggregateException aex)
    {
        /* handle aex.InnerExceptions (can be more than one) */
    }
    finally
    {
        button1.Enabled = true;
        Cursor = Cursors.Default;
    }
}
  1. Using the async and await keywords allows you to write asynchronous code that doesn't block the UI thread, in the same way that you would write normal synchronous code. The UI remains responsive while the asynchronous operation is in-flight.
  2. The Task.Run offloads the processing of the action delegate to a ThreadPool thread. The ThreadPool is a pool of reusable threads that is managed by the .NET infrastructure. In case it is necessary, you can fine-tune it with the ThreadPool.SetMinThreads method.
  3. The Parallel.ForEach invokes the body delegate in parallel on multiple ThreadPool threads, enforcing a MaxDegreeOfParallelism policy. In case you want unlimited parallelism, configure it with MaxDegreeOfParallelism = -1.

In case of an exception the Parallel.ForEach will stop invoking the body delegate, and will complete when all the currently running invocations have completed. The errors of all erroneous invocations will be bundled in an AggregateException. This exception will be propagated first by the Task.Run and then by the await operator, and you will be able to handle it on the UI thread with the try/catch block inside the Click event handler. You could also let it unhandled (omit the catch block), in which case it will be handled be the general Application.ThreadException event handler. If you omit this too, the user will be notified by the default error popup that the WinForms applications display by default, and gives the user the option to continue or exit the app.