控制台/asp.net 应用程序的自定义跨平台线程调度程序

Custom cross-platform thread dispatcher for console / asp.net application

我有一个 .NET 应用程序需要调用 COM 对象(它总是必须从同一个线程调用)。由于我在应用程序中有多个线程,我需要在另一个线程上调用一个操作。
该应用程序没有(标准)消息循环,我真的不喜欢添加 WPF/WinForms 只是为了有一个 Dispatcher.
实现允许在另一个线程上调用 Action / Func(具有 return 类型)的自定义“消息循环”/队列的安全有效方法是什么?
如果这个问题有一个跨平台的解决方案就好了。

根据@theodor-zoulias的信息,我想到了这个解决方案。
免责声明:可能这实际上是一个非常糟糕的设计!

public sealed class DispatcherLoop : IDisposable
{
    #region Instance
    private DispatcherLoop() { }

    static Dictionary<int, DispatcherLoop> dispatcherLoops = new();
    public static DispatcherLoop Current
    {
        get
        {
            int threadId = Thread.CurrentThread.ManagedThreadId;
            if (dispatcherLoops.ContainsKey(threadId))
                return dispatcherLoops[threadId];

            DispatcherLoop dispatcherLoop = new()
            {
                ThreadId = Thread.CurrentThread.ManagedThreadId
            };
            dispatcherLoops.Add(threadId, dispatcherLoop);
            return dispatcherLoop;
        }
    }
    #endregion

    bool isDisposed = false;
    public void Dispose()
    {
        if (isDisposed)
            throw new ObjectDisposedException(null);

        _queue.CompleteAdding();
        _queue.Dispose();
        dispatcherLoops.Remove(ThreadId);
        isDisposed = true;
    }

    public int ThreadId { get; private set; } = -1;
    public bool IsRunning { get; private set; } = false;

    BlockingCollection<Task> _queue = new();
    public void Run()
    {
        if (isDisposed)
            throw new ObjectDisposedException(null);

        if (ThreadId != Thread.CurrentThread.ManagedThreadId)
            throw new InvalidOperationException($"The {nameof(DispatcherLoop)} has been created for a different thread!");

        if (IsRunning)
            throw new InvalidOperationException("Already running!");

        IsRunning = true;

        try
        {
            // ToDo: `RunSynchronously` is not guaranteed to be executed on this thread (see comments below)!
            foreach (var task in _queue.GetConsumingEnumerable())
                task?.RunSynchronously();
        }
        catch (ObjectDisposedException) { }

        IsRunning = false;
    }

    public void BeginInvoke(Task task)
    {
        if (isDisposed)
            throw new ObjectDisposedException(null);

        if (!IsRunning)
            throw new InvalidOperationException("Not running!");

        if (ThreadId == Thread.CurrentThread.ManagedThreadId)
            task?.RunSynchronously();
        else
            _queue.Add(task);
    }

    public void Invoke(Action action)
    {
        if (isDisposed)
            throw new ObjectDisposedException(null);

        Task task = new(action);
        BeginInvoke(task);
        task.GetAwaiter().GetResult();
    }

    public T Invoke<T>(Func<T> action)
    {
        if (isDisposed)
            throw new ObjectDisposedException(null);

        Task<T> task = new(action);
        BeginInvoke(task);
        return task.GetAwaiter().GetResult();
    }
}

您应该使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive 并添加 using System.Reactive.Linq; - 然后您可以这样做:

var els = new EventLoopScheduler();

那么你可以这样做:

els.Schedule(() => Console.WriteLine("Hello, World!"));

EventLoopScheduler 启动自己的线程,您可以要求它安排您喜欢的任何工作 - 它永远是那个线程。

完成调度程序后,只需调用 els.Dispose() 即可将其完全关闭。

未来调度代码也有很多重载。很厉害class.