To learn Await/Async (and to have a tool I can use at work for some weird projects) I have made an object that marshals calls to its public methods into a specific thread to be run there. Essentially each public async method, when called, checks its current thread against the target. If the thread matches, it runs its code as normal. If it doesn't match, it closures its inputs into a lambda that is then stored in a queue being maintained by the target thread. Here is my prototype, with a single public method DoThing
.
public class ThreadedObject
{
private Thread thread;
private ConcurrentQueue<Task> concurrentQ = new();
public ThreadedObject()
{
thread = new(() => RunThread());
thread.IsBackground = true;
thread.Name = "ThreadedObjectRunner";
thread.Start();
}
private bool IsInWorkerThread()
{
return Thread.CurrentThread == thread;
}
private void RunThread()
{
while (true)
{
if (Thread.CurrentThread == thread)
{
Task task;
if (concurrentQ.TryDequeue(out task))
{
Debug.WriteLine(" Invoking a task");
task.RunSynchronously(new SynchronousScheduler());
}
}
else
{
Debug.Fail("Threaded object running outside its thread");
}
}
}
public async Task<int> DoThing(int length)
{
if (this.IsInWorkerThread())
{
Debug.WriteLine(" Running task of length " + length + " on thread " + Thread.CurrentThread.Name);
Thread.Sleep(length);
Debug.WriteLine(" Finished task of length " + length);
return length;
}
else
{
Debug.WriteLine("Queueing task of length " + length);
return await Enqueue(()=>DoThing(length).Result);
//return await Enqueue(async () => await DoThing(length)); //CS0029 cannot implicitly convert type 'void' to 'int'
}
}
private Task Enqueue(Action action)
{
Task later = new Task(() => action.Invoke());
concurrentQ.Enqueue(later);
return later;
}
private Task<dynamic> Enqueue(Func<dynamic> action)
{
Task<dynamic> later = new Task<dynamic>(() => action.Invoke());
concurrentQ.Enqueue(later);
return later;
}
private class SynchronousScheduler : TaskScheduler
{
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
protected override void QueueTask(Task task)
{
base.TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
base.TryExecuteTask(task);
return true;
}
}
}
So far this works as intended, surprisingly. However, I've been told not to use Task.Result
when I can avoid it since it can deadlock in certain cases, and I am using it in the return from DoThing
when queueing the lambda. Can/should/how would I replace that Task.Result
with an await
? You can see what I attempted commented out just below, but my grasp of what I'm doing is a bit too tenuous to get it to work.