In a C# Windows Store app that I am writing, I let the user tap a button and then I start a background task to do a bunch of work. While this working is being done, the user can see the results on-screen as they become available. But then at any time, the user can also change their configuration and tap the start button again. Or maybe I have a stop button to go with my stat button. That point is moot.

The problem is that the I cannot start another background task until the previous one has stopped.

StackOverflow Post About This Topic

The StackOverflow post in the link above does not answer the question. The given answer is mediocre because it tells the user how to write their code so that they don’t have to wait. That’s not an answer, it’s a work-around.

No Work-Arounds Here

xyz.Cancel();
Task.WaitAll( xyzt );

The above code does not work. The reason that it does not work is that the Cancel() method call causes a cancelation exception to be thrown in the task. I am not sure exactly where it is thrown and how it affects the background worker code, but it does get thrown. And then the WaitAll, or any other task wait function, will catch and rethrow that exception. Again, I don’t know the exact internal mechanism. What I do know is that the built-in cancelation feature and the Wait operation cannot be used together.

The Solution

The way to make this work as simply as possible, while also having an agile user interface, is to use an event to signal when the background task is done with all real processing. The event will get signaled before the background code is completed done, but it will get signaled after all possible conflicts are passed.

using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.System.Threading;

namespace MyNamespace
{
    class MyClass
    {
        private IAsyncAction m_Operation = null;

        private int m_Width = 0;
        private int m_Height = 0;
        AutoResetEvent m_TaskFinishedEvent = new AutoResetEvent( false );

        private MyClass() 
        {
        }

        private static Task WaitOnEventAsync( EventWaitHandle WaitForEvent )
        {
            return Task.Run( () => { WaitForEvent.WaitOne(); } );
        }

        public static async void Start( int Width, int Height )
        {
            if( Instance.m_Operation != null )
            {
                Instance.m_Operation.Cancel();

                // There is no way to wait for the cancelled worker to complete
                // because a cancellation exception has already been thrown in it.
                // Any Wait() call now will just throw that exception instead of 
                // waiting. Use our own wait mechanism for this.

                await WaitOnEventAsync( Instance.m_TaskFinishedEvent );
            }

            Instance.m_Width = Width;
            Instance.m_Height = Height;

            Instance.m_TaskFinishedEvent.Reset();

            Instance.StartWorkerThread();
        }

        private void StartWorkerThread()
        {
            m_Operation = ThreadPool.RunAsync( WorkerMethod, WorkItemPriority.Normal );
        }

        private void RealWork() 
        {
        }

        private static PuzzleGenerator _instance;
        public static PuzzleGenerator Instance
        {
            get 
            {
                if( _instance == null )
                {
                    _instance = new PuzzleGenerator();
                }
                return _instance;
            }
        }

        private void WorkerMethod( IAsyncAction operation )
        {
            m_Operation = operation;

            while (m_Operation.Status != AsyncStatus.Canceled)
                RealWork();

            m_TaskFinishedEvent.Set();
        }
    }
}

In the code above, I present a singleton class used to run a specific method as a background worker using the ThreadPool class. I use a singleton because the background working will produce results that affect the user interface. It is also necessary to wait for the current background processing to finish because this singleton holds a copy of the user settings that were passed in when the task was started.

The way to wait for the task to finish is to use an event. I use an AutoResetEvent for this purpose.

Line 15 shows the AutoResetEvent member variable called m_TaskFinishEvent.

Line 17 is the private constructor. This keeps the class from ever being instantiated outside of the singleton-specific code.

Line 21 is a method that can be used with the await keyword for waiting on the event. it takes an EventWaitHandle, which is the parent class to both a ManualResetEvent and an AutoResetEvent.

Line 26 is the start of the Start method. This static method is called by some other code to start the background processing.

Line 28 is where the code decides if it needs to wait for a background task to finish.

Line 37 is where the code will wait for the background task to finish. Note that it won’t actually wait here. it will return to the caller and then come back to the next line of code as soon as the WaitOnEventAsync method is finished. That is how the await keyword really works.

Before I go on about the rest of the code, a word of caution is needed. Since the processing will return to the caller from the location of the await keyword, it is possible for the user to tap a button and call the Start method again. The code should take steps to keep this from happening, such as placing a lock around the Start method code, or by using some other mechanism to return immediately if the Start method is called a second time. This is the nature of thread-safe coding.

Lines 43 and 45 are where I ensure that the event is reset, which is rather pointless, and then start the background thread. the background thread is stated with a call to a method that actually calls the threading code. I had more happening in that method in an earlier version, but it’s no longer necessary to separate this code into it’s own method.

Line 53 has the method that will hold the meat of the background processing code. this is where real work will get done.

Line 57 is where the singleton code starts. This just provides a way to make this a singleton and has nothing to do with the task management.

Line 70 has the code that runs the background processing loop. It relies on the built-in task cancelation mechanism for controlling it. Once it detects the cancellation request, it signals the m_TaskFinishedEvent event. this is what allows the other code to wait for the task to finish.

I provided all of the code because it is sometimes nice to see the full context of someone else’s code. it is not the best written code in the world, but it is a good start. I may tweak it a bit, but it will work fine as-is. I hope to have an app uploaded into the Windows Store that uses this very class. I’ll post about that when it happens.