2

I'm writing some code that is invoked on the UI thread, invokes some code on another thread (not the ThreadPool, but the same thread each time), and then resumes on the UI thread. I'd like some advice on the best async way to do this.

The complexity of the EnsureThread method is because the other thread must be the same thread every time and must be STA with a Dispatcher running on it. This is because I need to use MCI, but do not want it running on the UI thread. See here https://stackoverflow.com/a/32711239/420159.

I create the second thread like so:

private static void EnsureThread()
{
    if (eventLoopThread != null)
    {
        return;
    }

    lock (eventLoopLock)
    {
        if (eventLoopThread == null)
        {
            var lck = new EventWaitHandle(false, EventResetMode.ManualReset);

            var t = new Thread(() =>
            {
                try
                {
                    // create dispatcher and sync context
                    var d = Dispatcher.CurrentDispatcher;
                    var context = new DispatcherSynchronizationContext(d);
                    SynchronizationContext.SetSynchronizationContext(context);

                    // create taskfactory
                    eventLoopFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
                    eventLoopDispatcher = d;
                }
                finally
                {
                    lck.Set();
                }

                // run the event loop
                Dispatcher.Run();
            });
            t.SetApartmentState(ApartmentState.STA);
            t.IsBackground = true;
            t.Start();

            lck.WaitOne();
            lck.Dispose();

            eventLoopThread = t;
        }
    }
}

and then I call the second thread like so:

async void button_click(...)
{
    // do something 1

    await eventLoopFactory.StartNew(()=>
    {
        // do something 2
    });

    // do something 3
}

Is there a better way to do it?

Community
  • 1
  • 1
Daniel Crowe
  • 321
  • 3
  • 9
  • 1
    That `EnsureThread` methods looks very iffy to me. What are you trying to achieve? It is unclear at the moment. Please update the question. – Gusdor Sep 22 '15 at 08:00
  • Done. In short, I'm using mciSendString which requires a thread with an event loop, but do not want it running on the UI thread because it slows due the UI. – Daniel Crowe Sep 23 '15 at 23:55

2 Answers2

4

You don't really need to use a thread factory if you want to run a delegate on a thread of the ThreadPool

just use

        await Task.Run(() =>
        {
            // do something 2
        });

That way, you don't need to run your // do something code on an event loop thread, but it will be run on an available thread from the thread pool.

You shouldn't create a second thread yourself. Thread pools are the right tool for that, as it will recycle idle threads in a very efficient way.

Fabio Salvalai
  • 2,479
  • 17
  • 30
  • Hi, the target thread must **not** be the ThreadPool. It must be the same thread every time; hence the TaskScheduler. I'm using MCI and it needs to run on an STA thread with an event loop. Usually you would use the UI thread, but it is slowing down the UI and wish to move it off. – Daniel Crowe Sep 23 '15 at 23:46
  • Could you please explain why it really always need to run on the same thread? – Fabio Salvalai Sep 24 '15 at 05:12
  • I feel like I have done so. See the documentation for MCI (https://msdn.microsoft.com/en-us/library/dd757161(v=vs.85).aspx). Note the hwnd parameter, implying the requirement for an event loop. This method simply will not work on an arbitrary thread that is MTA or not running Dispatcher. – Daniel Crowe Sep 24 '15 at 05:37
  • Okay, got it! if I understand correctly, there are two things: Tasks are MTA by design, therefore, you can't use them. Second thing is: you want to avoid creating a new thread from scratch every single time you have to run your code, correct? For the first part, someone already solved it [here](http://stackoverflow.com/a/16722767/403098), but it doesn't help with the second, unfortunately. I'll try to think about something. – Fabio Salvalai Sep 24 '15 at 07:26
  • Thanks Fabio, but I've solved both the problems you've enumerated. The `EnsureThread` method keeps the thread STA and singleton. My problem is how to beat use Microsofts recommended asynchrony pattern. I've more experience with the EAP and APM asynchrony patterns. – Daniel Crowe Sep 24 '15 at 07:36
  • Sorry I can't help any further, Daniel. It seems the TPL way of doing asynchrony doesn't really play nice with legacy code requiring STA :( If you do find the answer, don't forget to post it here and accept your own answer. Good luck ! – Fabio Salvalai Sep 24 '15 at 07:46
1

Your Button_Click already seems to be doing way too many things, you should first split this method into separate methods.

async void Button_Click(...)
{
    await DoSomething1();
    await DoSomething2();
    await DoSomething3();
}

Now, your other methods will look something like this:

async Task DoSomething1()
{
    await Task.Run(() =>
    {
        ...
    });
}

This will allow your Button_Click method to asynchronously perform these tasks (in order) and keep your UI responsive.

Mike Eason
  • 9,525
  • 2
  • 38
  • 63
  • The example I gave is simplified to make it more readable. The actual method in question is more like: `TakeLock(); RunTask(); ReleaseLock();` – Daniel Crowe Sep 23 '15 at 23:50