您将如何编组对后台线程的调用并在 C# 的上下文中执行它? [英] How would you marshall a call to a background thread and execute it within its context in C#?

查看:33
本文介绍了您将如何编组对后台线程的调用并在 C# 的上下文中执行它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

现在假设我向集合中添加了一些东西(来自任一线程,无论哪个线程),现在它必须将回调编组到两个线程.要在 UI 的上下文中执行回调,我可以简单地执行 Dispatcher.Invoke(...) 并在 UI 的上下文中执行回调;伟大的.

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

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

我不断得到明显错误的答案,所以我一定没有正确解释自己.忘记 UI 线程和 UI 调度员吧,他们是为了将调用编组到 UI 线程,就是这样!想象两个工作线程 A 和 B.如果 A 修改了我的集合,则 A 负责将回调编组到自身和 B.在 A 的上下文中执行回调很容易,因为 A 是触发它的人:只需在适当的位置调用委托.现在 A 需要将回调编组到 B ......现在怎么办?Dispatcher 和 SynContext 在这种情况下没用.

解决方案

我们有一个必须始终在同一个 STA 后台线程上运行的组件.我们通过编写自己的SynchronizationContext 实现了这一点.这篇文章很有帮助.

总而言之,您不想中断您的工作线程,您希望它闲置等待它应该执行的下一个任务.您将作业添加到队列中,然后它会按顺序处理这些作业.SynchronizationContext 是围绕这个想法的一个方便的抽象.SynchronizationContext 是工作线程的所有者 - 外部世界不直接与线程交互:想要在工作线程上执行任务的调用者向添加作业的上下文发出请求到作业队列.Worker 要么正在工作,要么在轮询队列,直到添加另一个作业,此时它会再次开始工作.

更新

这是一个例子:

using System.Collections.Concurrent;使用 System.Threading;类 LoadBalancedContext : SynchronizationContext{只读线程 thread1;只读线程 thread2;readonly ConcurrentQueue作业 = 新的 ConcurrentQueue();公共负载平衡上下文(){this.thread1 = new Thread(this.Poll) { Name = "T1" };this.thread2 = new Thread(this.Poll) { Name = "T2" };this.thread1.Start();this.thread2.Start();}公共覆盖无效邮政(SendOrPostCallback d,对象状态){this.jobs.Enqueue(new JobInfo { Callback = d, State = state });}无效投票(){而(真){职位信息;if (this.jobs.TryDequeue(out info)){info.Callback(info.State);}线程睡眠(100);}}类 JobInfo{公共 SendOrPostCallback 回调 { get;放;}公共对象状态{获取;放;}}}

用法:

var context = new LoadBalancedContext();SendOrPostCallback 回调 = x =>{Trace.WriteLine(Thread.CurrentThread.Name);Thread.Sleep(200);};context.Post(回调,空);context.Post(callback, null);context.Post(回调,空);context.Post(回调,空);context.Post(callback, null);context.Post(回调,空);context.Post(回调,空);context.Post(回调,空);context.Post(回调,空);context.Post(回调,空);context.Post(回调,空);线程睡眠(1000);

Send 情况稍微复杂一些,因为您需要监听重置事件.这不是生产质量,但应该让您知道您需要做什么.

希望有所帮助.

Let's say I have a UI thread and a background thread that subscribe to a custom thread-safe ObservableCollection that I created so that whenever the collection changes it executes the callback within the appropriate context.

Now let's say I add something to the collection (from either thread, doesn't matter which one) and it now has to marshall the callback to both threads. To execute the callback within the UI's context I can simply do a Dispatcher.Invoke(...) and it executes the callback within the UI's context; great.

Now I want to execute the callback within the background thread's context (don't ask me why, it may well be that whatever it's accessing is affinitized to that specific thread or has thread-local storage it needs to access); how would I do that?

Background threads don't have a dispatcher/message pumping mechanism so I can't use a dispatcher or SynchronizationContext, so how would one interrupt a background thread and have it execute my callback within its context?

EDIT: I keep getting answers that are obviously wrong so I must not have explained myself correctly. Forget the UI thread and UI dispatchers guys, they were meant to marshall calls to the UI thread, that's it! Imagine two worker threads A and B. If A modifies my collection then A is in charge of marshalling the callback to itself and to B. Executing the callback within A's context is easy since A was the one triggering it : simply call the delegate in place. Now A needs to marshall the callback to B... now what? Dispatcher and SynContext are useless in this situation.

解决方案

We have a component that must always run on the same STA background thread. We've achieved this by writing our own SynchronizationContext. This article is very helpful.

To summarise, you don't want to interrupt your worker thread, you want it to sit idle waiting for the next task that it should execute. You add jobs to a queue and it processes those jobs in order. The SynchronizationContext is a convenient abstraction around that idea. The SynchronizationContext is the owner of the worker thread - and the outside world does not interact with the thread directly: callers who want to execute a task on the worker thread make the request to the context which adds the job to the job queue. The worker is either working or polling the queue until another job is added, at which point it begins working again.

Update

Here is an example:

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; }
    }
}

Usage:

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);

The Send case is slightly more involved as you will need to listen for a reset event.. This is not production quality, but should give you an idea ow what you need to do.

Hope that helps.

这篇关于您将如何编组对后台线程的调用并在 C# 的上下文中执行它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆