C#/WPF从另一个类中创建的另一个线程更新UI [英] C# / WPF Updating the UI from another thread created within another class

查看:102
本文介绍了C#/WPF从另一个类中创建的另一个线程更新UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我来自嵌入式C背景并且正在开发我的第一个C#应用程序,但对此我遇到了麻烦,但是我的研究尚未展开,所以我想在这里问.

I come from an embedded C background and I am working on my first C# application and I have hit a wall on this and my research is not panning out so I thought I would ask here.

到目前为止,简单的应用程序.我有一个MainWindow,在其他众多功能中,单击按钮即可启动TCPClient线程:

Simple app, so far. I have a MainWindow that, among a bunch of other stuff, starts a TCPClient thread on a button click:

public partial class MainWindow : Window
{
 ....
       TCPConnection myCon = new TCPConnection();
 ....
   private void connectButton_Click(object sender, RoutedEventArgs e)
    {
        networkListBox.Items.Add("Connecting...");
        myCon.Connect("localhost", updateNetworkListBox);
    }
 }

 ....
    public void updateNetworkListBox(string message)
    {
        networkListBox.Items.Add(message);
    }

在TCPConnection.cs中:

And in TCPConnection.cs:

   public class TCPConnection
   {
   ....
    public void Connect(string server, ReportDelegate reportDelegate)
    {
        this.server = server;
        clientThread = new Thread(() => Client(this.server));
        clientThread.Start();
        reportDelegate("Started client thread...");
    }

    static void Client(string server)
    {
        try
        {
            Int32 port = 25565;
            TcpClient client = new TcpClient(server, port);
            Byte[] outgoingBytes = new Byte[1024];
            string outgoingString = "Hello! I am " + Guid.NewGuid();
            outgoingBytes = System.Text.Encoding.ASCII.GetBytes(outgoingString);
            NetworkStream stream = client.GetStream();
            stream.Write(outgoingBytes, 0, outgoingBytes.Length);
            stream.Close();
            client.Close();
        }

我想做的第一件事,就是说TCP连接可以正常工作,是将一条消息发送回UI,例如客户端线程正在连接...",客户端线程已连接...",并将其显示在networkListbox.

The first thing I would like to do, now that TCP connection works is send a message back to the UI such as "Client thread connecting...", "Client thread connected..." and have it show up in the networkListbox.

在Connect()方法中,我可以通过使用委托来做到这一点,但是由于无法直接从另一个线程访问UI控件,因此在新线程中显然不起作用.

Within the Connect() method, I was able to do this by using the delegate but this obviously will not work in the new thread since one is not able to directly access UI controls from another thread.

我已经阅读了很多有关此的文章,并且我知道我可能想使用Dispatcher来执行此操作.但是,我见过的几乎所有示例都在当前类中创建了一个新线程,例如,将一个匿名方法传递给Dispatcher.Invoke().

I have read loads of articles on this and I know that I probably want to use the Dispatcher to to do this. However, almost all of the examples I have seen have created a new thread within the current class and, for example, passed an anonymous method to Dispatcher.Invoke().

的一个例外>提倡使用EventHandler并在主窗口中对其进行初始化的讨论.这似乎不太理想,但也许我错了.

One exception to this discussion which advocated using an EventHandler and initializing it in the main window. That seems less than ideal but maybe I am wrong.

进一步,有人提倡数据共享.再说一次,这对我来说似乎并不理想.

Further down, someone else advocated data sharing. Again, that seems less than ideal to me.

我读过的其他文章似乎过时了.

Other articles I have read appear to be out of date.

因此,我欢迎任何有关如何执行此操作的解释.可能是我只是在语法上被挂断了,但我怀疑,尽管我认为我对代表,lambda等几乎一清二楚,但我可能仍然挂在那到底需要完成什么.

So, I welcome any explanations on how to go about this. It may be that I am just getting hung up syntactically but I suspect that, although I think I am mostly clear on delegates, lambdas, etc., I am probably hung up on what exactly needs to get done.

如果您能通过一些解释说明如何在此特定示例中完成此操作,我将不胜感激.

If you can show how it would be done in this specific example with some explanation, I would greatly appreciate it.

也许还有一些对我有些朦胧的问题:

And maybe some specific questions on some points that are a little hazy for me:

1)我的工作人员任务可以单独访问吗,还是必须与UI的Dispatcher一起提供?

1) Can my worker task access on it on its own or must it be provided with the UI's Dispatcher?

2)UI应该提供一个执行调度的委托,还是应该在引用UI Dispatcher的worker任务中对调度进行编码?

2) Should the UI provide a delegate that performs the dispatch or should the dispatch be coded in the worker task, referencing the UI Dispatcher?

非常感谢.

推荐答案

对于您提供样本的问题,是否存在类似...的工人阶级.

For your question about providing a sample, if there is a worker class like...

public class Worker
{
    public Worker(Action<string>action)
    {
        Task.Run(() =>
        {
            int i = 0;
            while (true)
            {
                ++i;
                Task.Run(() => { action("Current value " + i); });
                Task.Run(() =>
                {
                    // doing some work here
                });
                Thread.Sleep(1000);
            }
        });
    }
}

...正在不同线程上执行后台工作,并通过委托为调用者提供建议.委托是一个普通的香草Action,它接受一个字符串.然后,应实现视图模型,以使其不关心消息起源于哪个线程.这是虚拟机中的相应代码...

...which is performing background work on different threads and advising the caller via the delegate. The delegate is a plain vanilla Action that takes a string. Then the View Model should be implemented such that it does not care on which thread the message originated. Here's the corresponding code in the VM...

    private readonly SynchronizationContext _context = SynchronizationContext.Current;
    private void StartWorker()
    {
        Worker w = new Worker((s) => _context.Post(delegate { StatusText = s; }, null));
    }

此代码使用SynchronizationContext,但也可以轻松使用调度程序.关键在于,UI线程上的同步责任不属于工作人员.工作者无需理会,并且类似地,VM是线程不可知的,并通过其SynchronizationContext发布一切.

This code uses a SynchronizationContext, but could just as easily use a dispatcher. The point being that the responsibility for sync'ing up on the UI thread doesn't belong to a worker. The worker shouldn't care, and similarly the VM is thread-agnostic and posts everything via its SynchronizationContext.

StatusText属性的代码如下所示……

The code for the StatusText property looks like this...

    private string _statusText;
    public string StatusText
    {
        [DebuggerStepThrough]
        get { return _statusText; }
        [DebuggerStepThrough]
        set
        {
            if (value != _statusText)
            {
                _statusText = value;
                OnPropertyChanged("StatusText");
            }
        }
    }

最后,在用户界面上,它的显示方式如下:

And finally, on the UI, it is presented like this...

        <StatusBar DockPanel.Dock="Bottom">
            <TextBlock Text="{Binding StatusText}"/>
        </StatusBar>

...

因此,要回顾一下您的问题:辅助线程可以访问它,但不必必须处理与UI同步的问题.那个责任是虚拟机的. VM应该是线程不可知的,并通过调度程序或同步上下文或其他方法来同步UI.

So to recap your questions: the worker threads can access it, but they should not have to deal with sync'ing up the UI. That responsibility is the VM's. And VM should be thread-agnostic and sync the UI through the dispatcher or synchronization context or other methods.

如果要处理作为绑定对象的集合(例如,ObservableCollection),则通过分派器进行调度是适当的;否则,SynchronizationContext是合适的(它要轻一些).

Scheduling through the Dispatcher is appropriate if you are manipulating a collection that is the subject of a binding (e.g., an ObservableCollection); otherwise SynchronizationContext is appropriate (it's a bit more light-weight).

这篇关于C#/WPF从另一个类中创建的另一个线程更新UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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