控制台/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.
我有一个 .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.