Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# can I start a thread and use a dispatcher to schedule work on it? [duplicate]

I'd like to have a dedicated thread I can schedule work on. Can I do this using dispatcher.invoke or do I have to do something custom?

It seems like dispatcher should work, but I can't figure out how to actually get the thread to start processing work from the dispatcher.

like image 301
AnthonyM Avatar asked Oct 31 '25 05:10

AnthonyM


1 Answers

The only problem I see is how to tell when the thread's dispatcher is up and running, and ready to accept commands. I added a Thread.Sleep(100) for this reason. Without it a TaskCanceledException is thrown.

Thread.CurrentThread.Name = "Main";
var thread = new Thread(() =>
{
    Console.WriteLine($"{Thread.CurrentThread.Name} Started");
    Dispatcher.Run();
    Console.WriteLine($"{Thread.CurrentThread.Name} Finished");
});
thread.IsBackground = true;
thread.Name = "Worker";
thread.Start();
Thread.Sleep(100);
Dispatcher dispatcher = Dispatcher.FromThread(thread);
dispatcher.Invoke(() =>
{
    Console.WriteLine($"Processed by {Thread.CurrentThread.Name}");
});
dispatcher.InvokeShutdown();
thread.Join();

Output:

Worker Started
Processed by Worker
Worker Finished


Update: A custom dispatcher could be implemented using a BlockingCollection. The class below has many missing features (cancellation, timeout, arguments, dispose etc), but offer the important advantage that can be shared by multiple worker threads.

public class CustomDispatcher
{
    private readonly BlockingCollection<(Action Action,
        TaskCompletionSource<bool> TCS)> _blockingCollection =
        new BlockingCollection<(Action, TaskCompletionSource<bool>)>();

    public void Run()
    {
        foreach (var item in _blockingCollection.GetConsumingEnumerable())
        {
            try
            {
                item.Action.Invoke();
                item.TCS.SetResult(true);
            }
            catch (Exception ex)
            {
                item.TCS.TrySetException(ex);
            }
        }
    }

    public Task InvokeAsync(Action action)
    {
        var tcs = new TaskCompletionSource<bool>();
        _blockingCollection.Add((action, tcs));
        return tcs.Task;
    }

    public void Invoke(Action action) => InvokeAsync(action).Wait();

    public void InvokeShutdown() => _blockingCollection.CompleteAdding();
}

Update: Regarding the build-in Dispatcher, instead of using the messy Thread.Sleep(100) to give time to the dispatcher to start, a TaskCompletionSource<Dispatcher> could be used for precise control:

Thread.CurrentThread.Name = "Main";
var dispatcherReady = new TaskCompletionSource<Dispatcher>();
var thread = new Thread(() =>
{
    Console.WriteLine($"{Thread.CurrentThread.Name} Started");
    dispatcherReady.SetResult(Dispatcher.CurrentDispatcher);
    Dispatcher.Run();
    Console.WriteLine($"{Thread.CurrentThread.Name} Finished");
});
thread.IsBackground = true;
thread.Name = "Worker";
thread.Start();
var dispatcher = dispatcherReady.Task.Result;
dispatcher.Invoke(() =>
{
    Console.WriteLine($"Processed by {Thread.CurrentThread.Name}");
});
dispatcher.InvokeShutdown();
thread.Join();

Output:

Worker Started
Processed by Worker
Worker Finished

like image 113
Theodor Zoulias Avatar answered Nov 02 '25 21:11

Theodor Zoulias



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!