mirror of
https://github.com/stride3d/stride.git
synced 2026-10-12 02:23:23 +00:00
334 lines
10 KiB
C#
334 lines
10 KiB
C#
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
|
|
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
|
|
#pragma warning disable SA1405 // Debug.Assert must provide message text
|
|
#pragma warning disable SA1402 // File may only contain a single class
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Stride.Core.Diagnostics;
|
|
|
|
namespace Stride.Core.MicroThreading;
|
|
|
|
/// <summary>
|
|
/// Represents an execution context managed by a <see cref="Scheduler"/>, that can cooperatively yield execution to another <see cref="MicroThread"/> at any point (usually using async calls).
|
|
/// </summary>
|
|
public class MicroThread
|
|
{
|
|
internal ProfilingKey? ProfilingKey;
|
|
|
|
/// <summary>
|
|
/// Gets the attached properties to this component.
|
|
/// </summary>
|
|
public PropertyContainer Tags;
|
|
|
|
private static long globalCounterId;
|
|
|
|
private long priority;
|
|
private int state;
|
|
private readonly CancellationTokenSource cancellationTokenSource;
|
|
private readonly SchedulerEntry schedulerEntry;
|
|
internal LinkedListNode<MicroThread> AllLinkedListNode; // Also used as lock for "CompletionTask"
|
|
internal MicroThreadCallbackList Callbacks;
|
|
internal SynchronizationContext? SynchronizationContext;
|
|
|
|
public MicroThread(Scheduler scheduler, MicroThreadFlags flags = MicroThreadFlags.None)
|
|
{
|
|
Id = Interlocked.Increment(ref globalCounterId);
|
|
Scheduler = scheduler;
|
|
schedulerEntry = new() { MicroThread = this };
|
|
AllLinkedListNode = new LinkedListNode<MicroThread>(this);
|
|
ScheduleMode = ScheduleMode.Last;
|
|
Flags = flags;
|
|
Tags = new PropertyContainer(this);
|
|
cancellationTokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the priority of this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The priority.
|
|
/// </value>
|
|
public long Priority
|
|
{
|
|
get { return priority; }
|
|
set
|
|
{
|
|
if (priority != value)
|
|
{
|
|
priority = value;
|
|
Scheduler.Reschedule(schedulerEntry, priority, ScheduleMode.First);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the id of this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The id.
|
|
/// </value>
|
|
public long Id { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the name of this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The name.
|
|
/// </value>
|
|
public string? Name { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the scheduler associated with this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <value>The scheduler associated with this <see cref="MicroThread"/>.</value>
|
|
public Scheduler Scheduler { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the state of this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <value>The state of this <see cref="MicroThread"/>.</value>
|
|
public MicroThreadState State { get { return (MicroThreadState)state; } internal set { state = (int)value; } }
|
|
|
|
/// <summary>
|
|
/// Gets the exception that was thrown by this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// It could come from either internally, or from <see cref="SetException"/> if it was successfully processed.
|
|
/// <value>The exception.</value>
|
|
public Exception? Exception { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="MicroThread"/> flags.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The flags.
|
|
/// </value>
|
|
public MicroThreadFlags Flags { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="MicroThread"/> scheduling mode.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The scheduling mode.
|
|
/// </value>
|
|
public ScheduleMode ScheduleMode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the task that will be executed upon completion (used internally for <see cref="Scheduler.WhenAll"/>)
|
|
/// </summary>
|
|
internal TaskCompletionSource<int>? CompletionTask { get; set; }
|
|
|
|
/// <summary>
|
|
/// A token for listening to the cancellation of the MicroThread.
|
|
/// </summary>
|
|
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
|
|
|
/// <summary>
|
|
/// Indicates whether the MicroThread is terminated or not, either in Completed, Canceled or Failed status.
|
|
/// </summary>
|
|
public bool IsOver
|
|
{
|
|
get
|
|
{
|
|
return
|
|
State is MicroThreadState.Completed or MicroThreadState.Canceled or MicroThreadState.Failed;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current micro thread (self).
|
|
/// </summary>
|
|
/// <value>The current micro thread (self).</value>
|
|
public static MicroThread? Current
|
|
{
|
|
get { return Scheduler.CurrentMicroThread; }
|
|
}
|
|
|
|
public void Migrate(Scheduler scheduler)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Remove()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts this <see cref="MicroThread"/> with the specified function.
|
|
/// </summary>
|
|
/// <param name="microThreadFunction">The micro thread function.</param>
|
|
/// <param name="scheduleMode">The schedule mode.</param>
|
|
/// <exception cref="System.InvalidOperationException">MicroThread was already started before.</exception>
|
|
public void Start(Func<Task> microThreadFunction, ScheduleMode scheduleMode = ScheduleMode.Last)
|
|
{
|
|
// TODO: Interlocked compare exchange?
|
|
if (Interlocked.CompareExchange(ref state, (int)MicroThreadState.Starting, (int)MicroThreadState.None) != (int)MicroThreadState.None)
|
|
throw new InvalidOperationException("MicroThread was already started before.");
|
|
|
|
Func<Task> wrappedMicroThreadFunction = async () =>
|
|
{
|
|
try
|
|
{
|
|
State = MicroThreadState.Running;
|
|
|
|
await microThreadFunction();
|
|
|
|
if (State != MicroThreadState.Running)
|
|
throw new InvalidOperationException("MicroThread completed in an invalid state.");
|
|
State = MicroThreadState.Completed;
|
|
}
|
|
catch (OperationCanceledException e)
|
|
{
|
|
// Exit gracefully on cancellation exceptions
|
|
SetException(e);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Scheduler.Log.Error("Unexpected exception while executing a micro-thread.", e);
|
|
SetException(e);
|
|
}
|
|
finally
|
|
{
|
|
lock (Scheduler.AllMicroThreads)
|
|
{
|
|
Scheduler.AllMicroThreads.Remove(AllLinkedListNode);
|
|
}
|
|
}
|
|
};
|
|
|
|
Action callback = () =>
|
|
{
|
|
SynchronizationContext = new MicroThreadSynchronizationContext(this);
|
|
SynchronizationContext.SetSynchronizationContext(SynchronizationContext);
|
|
|
|
wrappedMicroThreadFunction();
|
|
};
|
|
|
|
lock (Scheduler.AllMicroThreads)
|
|
{
|
|
Scheduler.AllMicroThreads.AddLast(AllLinkedListNode);
|
|
}
|
|
|
|
ScheduleContinuation(scheduleMode, callback);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Yields to this <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
public async Task Run()
|
|
{
|
|
Scheduler.Reschedule(schedulerEntry, Priority, ScheduleMode.First);
|
|
var currentScheduler = Scheduler.Current;
|
|
if (currentScheduler == Scheduler)
|
|
await Scheduler.Yield();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels the <see cref="MicroThread"/>.
|
|
/// </summary>
|
|
public void Cancel()
|
|
{
|
|
// TODO: If we unschedule the microthread after cancellation, we never give user code the chance to throw OperationCanceledException.
|
|
// If we don't, we can't be sure that the MicroThread ends.
|
|
// Should we run continuations manually?
|
|
|
|
// Notify awaitables
|
|
cancellationTokenSource.Cancel();
|
|
|
|
// Unschedule microthread
|
|
//lock (Scheduler.scheduledMicroThreads)
|
|
//{
|
|
// if (ScheduledLinkedListNode.Index != -1)
|
|
// {
|
|
// Scheduler.scheduledMicroThreads.Remove(ScheduledLinkedListNode);
|
|
// }
|
|
//}
|
|
}
|
|
|
|
internal void SetException(Exception exception)
|
|
{
|
|
Exception = exception;
|
|
|
|
// Depending on if exception was raised from outside or inside, set appropriate state
|
|
State = (exception is OperationCanceledException) ? MicroThreadState.Canceled : MicroThreadState.Failed;
|
|
}
|
|
|
|
internal void ScheduleContinuation(ScheduleMode scheduleMode, SendOrPostCallback callback, object? callbackState)
|
|
{
|
|
Debug.Assert(callback != null);
|
|
var node = Scheduler.NewCallback();
|
|
node.SendOrPostCallback = callback;
|
|
node.CallbackState = callbackState;
|
|
Scheduler.Schedule(ref Callbacks, node, schedulerEntry, priority, scheduleMode);
|
|
}
|
|
|
|
internal void ScheduleContinuation(ScheduleMode scheduleMode, Action callback)
|
|
{
|
|
Debug.Assert(callback != null);
|
|
var node = Scheduler.NewCallback();
|
|
node.MicroThreadAction = callback;
|
|
Scheduler.Schedule(ref Callbacks, node, schedulerEntry, priority, scheduleMode);
|
|
}
|
|
}
|
|
|
|
internal class MicroThreadCallbackNode
|
|
{
|
|
public Action? MicroThreadAction;
|
|
|
|
public SendOrPostCallback? SendOrPostCallback;
|
|
|
|
public object? CallbackState;
|
|
|
|
public MicroThreadCallbackNode? Next;
|
|
|
|
public void Invoke()
|
|
{
|
|
if (MicroThreadAction != null)
|
|
{
|
|
MicroThreadAction();
|
|
}
|
|
else
|
|
{
|
|
SendOrPostCallback?.Invoke(CallbackState);
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
MicroThreadAction = null;
|
|
SendOrPostCallback = null;
|
|
CallbackState = null;
|
|
}
|
|
}
|
|
|
|
internal struct MicroThreadCallbackList
|
|
{
|
|
public MicroThreadCallbackNode? First { get; private set; }
|
|
|
|
public MicroThreadCallbackNode Last { get; private set; }
|
|
|
|
public void Add(MicroThreadCallbackNode node)
|
|
{
|
|
if (First == null)
|
|
First = node;
|
|
else
|
|
Last.Next = node;
|
|
|
|
Last = node;
|
|
}
|
|
|
|
public bool TakeFirst([NotNullWhen(true)] out MicroThreadCallbackNode? callback)
|
|
{
|
|
callback = First;
|
|
|
|
if (callback == null)
|
|
return false;
|
|
|
|
First = callback.Next;
|
|
callback.Next = null;
|
|
|
|
return true;
|
|
}
|
|
}
|