Search code examples
c#multithreading.net-3.5

Waiting for and terminating a thread after a given time without blocking in .NET 3.5


I have a WinForms application on .NET 3.5. In this form, the user triggers an operation which is executed in another thread (a BackgroundWorker to be precise) so as to not block the UI thread. I'm in MVP, so all this is being done by a presenter which interacts with an interface to the view (implemented by the Windows Form). So far so good.

I would like to introduce functionality whereby a timeout period is introduced for the background operation to complete before cancelling it. Sounds simple enough. But the background operation calls a single function on a third-party component which may never return, so the cancellation capabilities of the BackgroundWorker are of no use to me here. Also, the BackgroundWorker.RunWorkerCompleted allowed me to get back on the UI thread, so I need to wait for the timeout or success and be able to get back to my calling thread (namely the UI thread).

I tried this using a plain old Thread (which does support Abort()) and a Timer running on a second thread, but can't seem to get it to work quite right since Join() is blocking my UI thread despite the description stating that it will block "while continuing to perform standard COM and SendMessage pumping". Admittedly I assumed this implied that it would continue to process Windows Messages, which was not the case.

int timeoutInMsec = 10000;
Thread connectThread = new Thread(Connect);
Thread timerThread = new Thread(() =>
    {
        var timer = new System.Windows.Forms.Timer() { Interval = timeoutInMsec };
        timer.Tick += (_s, _e) => 
            {
                timer.Stop();
                if (connectThread.ThreadState == ThreadState.Running)
                    connectThread.Abort();
            };
    };

connectThread.Start();
timerThread.Start();
timerThread.Join();
connectThread.Join();

Based on this question, I tried removing the second timer thread and adding a ManualResetEvent and calling Set() when the timer ticked, or when the Connect method did indeed complete. Here, instead of Join I used WaitOne, but unfortunately this also blocks my UI thread. I also found this other question, which a CancellationTokenSource which unfortunately is not available in .NET 3.5.

So, how can I spin my worker up and be able to terminate it after a given amount of time in .NET 3.5, while at the same time be able to get back to the thread where I spun up the worker thread to execute a sort of OnCompleted handler?

Many thanks in advance!

PS: I don't have a lot of experience in multi-threaded programming in .NET, so I'm sorry if this is trivial.


Solution

  • If I understood your question correctly, the following algorithm should solve your problem:

    • As before, create a BackgroundWorker to do your background work.

    • In BackgroundWorker_DoWork,

      • create a new thread (let's call it the "third-party thread") to call your third-party library, and then
      • wait for the third-party thread to finish or the timeout to elapse. (*)

    That way, your UI won't block, since only the Backgroundworker thread is waiting, not the main thread.

    Now about the interesting part: How do you wait for the third-party thread to finish (the step marked with (*))?

    My suggestion would be to simply use "loop waiting with sleep", i.e. (pseudo-code, you can use the Stopwatch class for the timeout):

    do until (third-party thread has finished or x seconds have elapsed):
        Thread.Sleep for 100ms
    
    if third-party thread has not finished:
        Abort it     // we don't have another choice
    else
        Process the result
    

    It's not best practice, but it's simple, it gets the job done and you can always replace it with fancy cross-thread-syncronization stuff (which is non-trivial to get right) once you got it all working.