假设我有一个UI线程和一个后台线程,它订阅了我创建的自定义线程安全的ObservableCollection,这样无论何时集合发生更改,它都会在适当的上下文中执行回调。
现在让我说我在集合中添加了一些东西(来自任何一个线程,并不重要),它现在必须将回调编组到两个线程。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(...)并在UI的上下文中执行回调;大。
现在我想在后台线程的上下文中执行回调(不要问我为什么,很可能是它访问的任何内容都与该特定线程关联或者有线程 - 它需要访问的本地存储);我该怎么办?
后台线程没有调度程序/消息提取机制所以我不能使用调度程序或SynchronizationContext,那么如何中断后台线程并让它在其上下文中执行我的回调?
编辑:我一直得到明显错误的答案,所以我不能正确解释自己。忘记UI线程和UI调度员,他们本来打算调用UI线程,这就是它!想象一下两个工作线程A和B.如果A修改了我的集合,那么A负责将回调编组到自身和B.在A的上下文中执行回调很容易,因为A是触发它的人:简单地调用代表到位。现在A需要将回调编组到B ......现在是什么?在这种情况下,Dispatcher和SynContext 无用。答案 0 :(得分:2)
一个好主意也可能是扩展自己的TaskScheduler,你必须实现三种方法:
QueueTask,TryExecuteTaskInline和GetScheduledTasks
你可以阅读here
这样,只要您需要在专用线程上运行某些东西,您就可以这样做:
Task.Factory.StartNew(() => { SomeAction }, SomeCancellationToken, TaskCreationOptions
new MyTaskSchedular());
让它在你的线程上执行。
答案 1 :(得分:0)
我们有一个必须始终在同一个STA后台线程上运行的组件。我们通过编写自己的SynchronizationContext
来实现这一目标。这个article非常有用。
总而言之,您不希望中断您的工作线程,您希望它处于空闲状态,等待它应执行的下一个任务。您将作业添加到队列,并按顺序处理这些作业。 SynchronizationContext
是围绕这个想法的方便抽象。 SynchronizationContext
是工作线程的所有者 - 外部世界不直接与线程交互:想要在工作线程上执行任务的调用者向上下文发出请求,将作业添加到作业中队列。工作人员正在工作或轮询队列,直到添加另一个作业,此时它再次开始工作。
<强>更新强>
以下是一个例子:
using System.Collections.Concurrent;
using System.Threading;
class LoadBalancedContext : SynchronizationContext
{
readonly Thread thread1;
readonly Thread thread2;
readonly ConcurrentQueue<JobInfo> jobs = new ConcurrentQueue<JobInfo>();
public LoadBalancedContext()
{
this.thread1 = new Thread(this.Poll) { Name = "T1" };
this.thread2 = new Thread(this.Poll) { Name = "T2" };
this.thread1.Start();
this.thread2.Start();
}
public override void Post(SendOrPostCallback d, object state)
{
this.jobs.Enqueue(new JobInfo { Callback = d, State = state });
}
void Poll()
{
while (true)
{
JobInfo info;
if (this.jobs.TryDequeue(out info))
{
info.Callback(info.State);
}
Thread.Sleep(100);
}
}
class JobInfo
{
public SendOrPostCallback Callback { get; set; }
public object State { get; set; }
}
}
用法:
var context = new LoadBalancedContext();
SendOrPostCallback callback = x =>
{
Trace.WriteLine(Thread.CurrentThread.Name);
Thread.Sleep(200);
};
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
Thread.Sleep(1000);
由于您需要监听重置事件,因此Send
案例稍微复杂一些。这不是生产质量,但应该让您知道自己需要做什么。
希望有所帮助。
答案 2 :(得分:0)
忘记dispatcher.invoke,忘记ui线程。想象一下,我有2个工人 线程,我想将我的事件发送给两个工作线程;什么 我可以用吗?
我为此使用了两个任务调度程序(如@YuvalItzchakov's answer建议的那样),每个线程一个。我还为工作线程使用自定义同步上下文,如@TheMouthofaCow's answer所示。
也就是说,对于UI线程,我只是保存并使用TaskScheduler.FromCurrentSynchronizationContext()
。对于工作线程,我会启动一个线程并在其上安装自定义同步上下文,然后也使用FromCurrentSynchronizationContext
。
像这样(未经测试):
// UI thread
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
using (var worker = new ThreadWithPumpingSyncContext())
{
// call the worker thread
var result = await worker.Run(async () =>
{
// worker thread
await Task.Delay(1000);
// call the UI thread
await Task.Factory.StartNew(async () =>
{
// UI thread
await Task.Delay(2000);
MessageBox.Show("UI Thread!"),
// call the worker thread
await worker.Run(() =>
{
// worker thread
Thread.Sleep(3000)
});
// UI thread
await Task.Delay(4000);
}, uiTaskScheduler).Unwrap();
// worker thread
await Task.Delay(5000);
return Type.Missing; // or implement a non-generic version of Run
});
}
// ...
// ThreadWithSerialSyncContext renamed to ThreadWithPumpingSyncContext
class ThreadWithPumpingSyncContext : SynchronizationContext, IDisposable
{
public readonly TaskScheduler Scheduler; // can be used to run tasks on the pumping thread
readonly Task _mainThreadTask; // wrap the pumping thread as Task
readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
// track async void methods
readonly object _lock = new Object();
volatile int _pendingOps = 0; // the number of pending async void method calls
volatile TaskCompletionSource<Empty> _pendingOpsTcs = null; // to wait for pending async void method calls
public ThreadWithPumpingSyncContext()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
_mainThreadTask = Task.Factory.StartNew(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(this);
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
// pumping loop
foreach (var action in _actions.GetConsumingEnumerable())
action();
}
finally
{
SynchronizationContext.SetSynchronizationContext(null);
}
}, TaskCreationOptions.LongRunning);
Scheduler = tcs.Task.Result;
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
lock (_lock)
{
if (_pendingOpsTcs != null && _pendingOpsTcs.Task.IsCompleted)
throw new InvalidOperationException("OperationStarted"); // shutdown requested
_pendingOps++;
}
}
public override void OperationCompleted()
{
lock (_lock)
{
_pendingOps--;
if (0 == _pendingOps && null != _pendingOpsTcs)
_pendingOpsTcs.SetResult(Empty.Value);
}
}
public override void Post(SendOrPostCallback d, object state)
{
_actions.Add(() => d(state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
// Task start helpers
public Task Run(Action action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler);
}
public Task Run(Func<Task> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
public Task<T> Run<T>(Func<Task<T>> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
// IDispose
public void Dispose()
{
var disposingAlready = false;
lock (_lock)
{
disposingAlready = null != _pendingOpsTcs;
if (!disposingAlready)
{
// do not allow new async void method calls
_pendingOpsTcs = new TaskCompletionSource<Empty>();
if (0 == _pendingOps)
_pendingOpsTcs.TrySetResult(Empty.Value);
}
}
// outside the lock
if (!disposingAlready)
{
// wait for pending async void method calls
_pendingOpsTcs.Task.Wait();
// request the end of the pumping loop
_actions.CompleteAdding();
}
_mainThreadTask.Wait();
}
struct Empty { public static readonly Empty Value = default(Empty); }
}
这为您提供了两种线程之间的某种协作异步执行。