Add ITaskManager.BlockWaitOnTask()

Smugleaf needed this.

Also changed RobustSynchronizationContext to use channels instead of ConcurrentQueue internally.
This commit is contained in:
Pieter-Jan Briers
2022-03-26 17:01:45 +01:00
parent 00b557b77a
commit 1f199ea71c
2 changed files with 47 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
namespace Robust.Shared.Asynchronous
@@ -14,10 +15,19 @@ namespace Robust.Shared.Asynchronous
public RobustSynchronizationContext(IRuntimeLog runtimeLog)
{
_runtimeLog = runtimeLog;
var channel = Channel.CreateUnbounded<Mail>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = false
});
_channelReader = channel.Reader;
_channelWriter = channel.Writer;
}
private readonly ConcurrentQueue<(SendOrPostCallback d, object? state)> _pending
= new();
private readonly ChannelReader<Mail> _channelReader;
private readonly ChannelWriter<Mail> _channelWriter;
public override void Send(SendOrPostCallback d, object? state)
{
@@ -34,18 +44,18 @@ namespace Robust.Shared.Asynchronous
public override void Post(SendOrPostCallback d, object? state)
{
_pending.Enqueue((d, state));
_channelWriter.TryWrite(new Mail(d, state));
}
public void ProcessPendingTasks()
{
while (_pending.TryDequeue(out var task))
while (_channelReader.TryRead(out var task))
{
#if EXCEPTION_TOLERANCE
try
#endif
{
task.d(task.state);
task.Callback(task.State);
}
#if EXCEPTION_TOLERANCE
catch (Exception e)
@@ -55,5 +65,12 @@ namespace Robust.Shared.Asynchronous
#endif
}
}
public ValueTask<bool> WaitOnPendingTasks()
{
return _channelReader.WaitToReadAsync();
}
private record struct Mail(SendOrPostCallback Callback, object? State);
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
@@ -32,10 +33,21 @@ namespace Robust.Shared.Asynchronous
_mainThreadContext.Post(_runCallback, callback);
}
private static readonly SendOrPostCallback _runCallback = o =>
public void BlockWaitOnTask(Task task)
{
((Action?)o)?.Invoke();
};
// NOTE: This code should be re-entry safe.
while (true)
{
var waitTask = _mainThreadContext.WaitOnPendingTasks().AsTask();
var idx = Task.WaitAny(task, waitTask);
if (idx == 0)
return;
_mainThreadContext.ProcessPendingTasks();
}
}
private static readonly SendOrPostCallback _runCallback = o => { ((Action?)o)?.Invoke(); };
}
public interface ITaskManager
@@ -52,5 +64,14 @@ namespace Robust.Shared.Asynchronous
/// </remarks>
/// <param name="callback">The callback that will be invoked on the main thread.</param>
void RunOnMainThread(Action callback);
/// <summary>
/// Synchronously wait for a main-thread task to complete.
/// This is effectively what you need to safely .Result a task on the main thread.
/// </summary>
/// <remarks>
/// Use of this method is only ever recommended in rare scenarios like shutdown. For most other scenarios you should really avoid blocking the main thread and use proper async instead.
/// </remarks>
void BlockWaitOnTask(Task task);
}
}