Need a clean way to handshake between two executing threads in C#

Viewed 331

A have a need to force certain functions of my code to run on the main thread. We are talking to a single thread black box and if I run from a worker thread my performance is unacceptable. I had been working around this for months by saving the current synchronization context and then using Post to call a function that would then execute in the main thread. I know this is meant for GUIs and I'm not sure why it had been working to date but it has now been inconsistent when we go to older versions of our base software which I must be compatible with.

I have written this basic example that shows my issue. I run a task that runs in a new thread and at a certain point I want that thread to be able to ask the main thread to perform some actions and then I want the worker thread to be able to continue along.

This is the current output:

Main (ThreadId = 1)
RunTask (ThreadId = 3)
CallBack (ThreadId = 4)  << I want this to be ThreadId 1

Any help would be great and even better if it is something close to the current solution because we were days from release and I'm worried a major rewrite could cause more problems. Thanks!

public class Test
{
    internal static SynchronizationContext _context;    
    internal static bool _busy = false;

    static void Main(string[] args)
    {
        Console.WriteLine("Main (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());

        _context = SynchronizationContext.Current;

        Task.Run(() => RunTask());

        Console.Read();
    }

    public static Task RunTask()
    {
        Console.WriteLine("RunTask (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

        _busy = true;

        _context.Post(new SendOrPostCallback((o) =>
        {
            CallBack(null,
                     EventArgs.Empty);
        }),
                       null);

        while (_busy == true)
        {

        }

        return null;
    }

    public static void CallBack(object sender, EventArgs e)
    {
        Console.WriteLine("CallBack (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");
    }
}
2 Answers

You can try to pass a delegate to main thread through event:

public class Test
{
    public static BlockingCollection<Action> Handlers = new BlockingCollection<Action>();

    static void Main(string[] args)
    {
        Console.WriteLine("Main (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");
        var task = new TaskWrapper();
        task.CallBack += OnCallBack;
        task.Run();

        while (true)
        {
            var action = Handlers.Take();
            action();
        }
    }

    public static void OnCallBack(object sender, Action a)
    {
        Handlers.Add(a);
    }
}

public class TaskWrapper
{
    public event EventHandler<Action> CallBack;

    public TaskWrapper()
    {
        CallBack += (sender, args) => { };
    }

    public void Run()
    {
        Task.Run(() =>
        {
            Console.WriteLine("RunTask (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

            CallBack(this, () => Console.WriteLine("CallBack (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")"));

            while (true)
            {

            }
        });
    }
}

Also take a look on this question and this article about implementation of synchronization context in console application.

Thank you all for the suggestions. I eventually found this:

https://stackoverflow.com/a/20071498/10312402

and a similar solution fixes my issue. Here is my sample program now:

public static class DispatcherHelper
{
    public static void DoEvents()
    {
        DispatcherFrame frame = new DispatcherFrame();
        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
        Dispatcher.PushFrame(frame);
    }

    private static object ExitFrame(object frame)
    {
        ((DispatcherFrame)frame).Continue = false;
        return null;
    }
}

public class Test
{
    internal static bool _busy = false;

    internal static Dispatcher _dispatcher;

    static void Main(string[] args)
    {
        Console.WriteLine("Main (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

        _dispatcher = Dispatcher.CurrentDispatcher;

        Task.Run(() => RunTask());

        DispatcherHelper.DoEvents();
        DispatcherHelper.DoEvents();
    }

    public static Task RunTask()
    {
        Console.WriteLine("RunTask (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

        _busy = true;

        _dispatcher.Invoke(new Action(CallBack));

        while (_busy == true)
        {

        }

        return null;
    }

    public static void CallBack()
    {
        Console.WriteLine("CallBack (ThreadId = " + Thread.CurrentThread.ManagedThreadId + ")");

        _busy = false;
    }
}

With that code I get my desired output:

Main (ThreadId = 1)
RunTask (ThreadId = 3)
CallBack (ThreadId = 1)

Plugging it back into my full program also works.

Related