您将如何处理对后台线程的调用,并在C#中的上下文中执行它?

您将如何处理对后台线程的调用,并在C#中的上下文中执行它?,c#,multithreading,synchronization,C#,Multithreading,Synchronization,假设我有一个UI线程和一个后台线程,它们订阅了我创建的自定义线程安全的ObservableCollection,因此每当集合发生更改时,它都会在适当的上下文中执行回调 现在让我们假设我向集合中添加了一些东西(从任何一个线程,不管是哪个线程),它现在必须对两个线程的回调进行马歇尔处理。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(…)并在UI的上下文中执行回调;太好了 现在,我想在后台线程的上下文中执行回调(不要问我为什么,很可能是它访问的任何线程都与该特定线程密

假设我有一个UI线程和一个后台线程,它们订阅了我创建的自定义线程安全的ObservableCollection,因此每当集合发生更改时,它都会在适当的上下文中执行回调

现在让我们假设我向集合中添加了一些东西(从任何一个线程,不管是哪个线程),它现在必须对两个线程的回调进行马歇尔处理。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(…)并在UI的上下文中执行回调;太好了

现在,我想在后台线程的上下文中执行回调(不要问我为什么,很可能是它访问的任何线程都与该特定线程密切相关,或者它需要访问线程本地存储);我该怎么做

后台线程没有dispatcher/消息泵机制,因此我不能使用dispatcher或SynchronizationContext,那么如何中断后台线程并让它在其上下文中执行回调


编辑:我不断得到明显错误的答案,所以我一定没有正确地解释自己。忘了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); }
}