你如何编组一个后台线程的调用并在C#的上下文中执行它?

时间:2014-04-11 07:44:48

标签: c# multithreading synchronization

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

现在让我说我在集合中添加了一些东西(来自任何一个线程,并不重要),它现在必须将回调编组到两个线程。要在UI的上下文中执行回调,我可以简单地执行Dispatcher.Invoke(...)并在UI的上下文中执行回调;大。

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

后台线程没有调度程序/消息提取机制所以我不能使用调度程序或SynchronizationContext,那么如何中断后台线程并让它在其上下文中执行我的回调?

编辑:我一直得到明显错误的答案,所以我不能正确解释自己。忘记UI线程和UI调度员,他们本来打算调用UI线程,这就是它!想象一下两个工作线程A和B.如果A修改了我的集合,那么A负责将回调编组到自身和B.在A的上下文中执行回调很容易,因为A是触发它的人:简单地调用代表到位。现在A需要将回调编组到B ......现在是什么?在这种情况下,Dispatcher和SynContext 无用

3 个答案:

答案 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); }
}

这为您提供了两种线程之间的某种协作异步执行。