您将如何处理对后台线程的调用,并在C#中的上下文中执行它?
假设我有一个UI线程和一个后台线程,它们订阅了我创建的自定义线程安全的ObservableCollection,因此每当集合发生更改时,它都会在适当的上下文中执行回调 现在让我们假设我向集合中添加了一些东西(从任何一个线程,不管是哪个线程),它现在必须对两个线程的回调进行马歇尔处理。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(…)并在UI的上下文中执行回调;太好了 现在,我想在后台线程的上下文中执行回调(不要问我为什么,很可能是它访问的任何线程都与该特定线程密切相关,或者它需要访问线程本地存储);我该怎么做 后台线程没有dispatcher/消息泵机制,因此我不能使用dispatcher或SynchronizationContext,那么如何中断后台线程并让它在其上下文中执行回调您将如何处理对后台线程的调用,并在C#中的上下文中执行它?,c#,multithreading,synchronization,C#,Multithreading,Synchronization,假设我有一个UI线程和一个后台线程,它们订阅了我创建的自定义线程安全的ObservableCollection,因此每当集合发生更改时,它都会在适当的上下文中执行回调 现在让我们假设我向集合中添加了一些东西(从任何一个线程,不管是哪个线程),它现在必须对两个线程的回调进行马歇尔处理。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(…)并在UI的上下文中执行回调;太好了 现在,我想在后台线程的上下文中执行回调(不要问我为什么,很可能是它访问的任何线程都与该特定线程密
编辑:我不断得到明显错误的答案,所以我一定没有正确地解释自己。忘了UI线程和UI分派器吧,他们本来是要处理对UI线程的调用的,就是这样!想象两个工作线程A和B。如果A修改了我的集合,那么A负责将回调编组到自身和B。在A的上下文中执行回调很容易,因为A是触发回调的线程:只需就地调用委托即可。现在A需要将回调整理成B。。。现在怎么办?Dispatcher和SynContext在这种情况下是无用的。我们有一个组件必须始终在同一个STA后台线程上运行。我们通过编写自己的
SynchronizationContext
实现了这一点。这很有帮助
总之,您不希望中断工作线程,而是希望它空闲地等待下一个应该执行的任务。将作业添加到队列中,队列将按顺序处理这些作业。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; }
}
}
Send
案例稍微复杂一些,因为您需要监听重置事件。。这不是生产质量,但应该让你知道你需要做什么
希望对您有所帮助。扩展您自己的TaskScheduler也是一个好主意,您必须实现三种方法: QueueTask、TryExecuteTaskInline和GetScheduledTasks 你可以读到它 这样,只要您需要在专用线程上运行某些内容,您就可以执行以下操作:
Task.Factory.StartNew(() => { SomeAction }, SomeCancellationToken, TaskCreationOptions
new MyTaskSchedular());
让它在你的线程上执行
忘记dispatcher.invoke,忘记ui线程。假设我有两个工人
线程,我想将我的事件分派给两个工作线程;什么
我能用吗
我会使用两个任务调度器(如建议的那样),每个线程一个。正如建议的那样,我还将为工作线程使用自定义同步上下文
也就是说,对于UI线程,我只需保存并使用TaskScheduler.FromCurrentSynchronizationContext()
。对于工作线程,我将启动一个线程并在其上安装自定义同步上下文,然后也使用SynchronizationCurrentContext的fromSynchronizationContext
类似这样(未经测试):
//UI线程
var uiTaskScheduler=TaskScheduler.FromCurrentSynchronizationContext();
使用(var worker=new ThreadWithPumpingSyncContext())
{
//调用工作线程
var result=await worker.Run(异步()=>
{
//工作线程
等待任务。延迟(1000);
//调用UI线程
等待Task.Factory.StartNew(异步()=>
{
//用户界面线程
等待任务。延迟(2000);
MessageBox.Show(“UI线程!”),
//调用工作线程
等待工作进程。运行(()=>
{
//工作线程
线程。睡眠(3000)
});
//用户界面线程
等待任务。延迟(4000);
},uiTaskScheduler)。展开();
//工作线程
等待任务。延迟(5000);
返回类型。缺少;//或实现运行的非泛型版本
});
}
// ...
//ThreadWithSerialSyncContext重命名为ThreadWithPumpingSyncContext
类ThreadWithPumpingSyncContext:SynchronizationContext,IDisposable
{
public readonly TaskScheduler;//可用于在泵送线程上运行任务
只读任务_mainThreadTask;//将泵送线程包装为任务
只读BlockingCollection _actions=新建BlockingCollection();
//跟踪异步void方法
只读对象_lock=新对象();
volatile int _pendingOps=0;//挂起的异步void方法调用数
volatile TaskCompletionSource _pendingOpsTcs=null;//等待挂起的异步void方法调用
public ThreadWithPumpingSyncContext()
{
var tcs=new TaskCompletionSource();
_mainThreadTask=Task.Factory.StartNew(()=>
{
尝试
{
SynchronizationContext.SetSynchronizationContext(此);
SetResult(TaskScheduler.FromCurrentSynchronizationContext());
//泵送回路
foreach(在_actions.getconsumineGenumerable()中的var action)
动作();
}
最后
{
SynchronizationContext.SetSynchronizationContext(空);
}
},TaskCreationOpti
Task.Factory.StartNew(() => { SomeAction }, SomeCancellationToken, TaskCreationOptions
new MyTaskSchedular());
// 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); }
}