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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With