【问题标题】:C# / WPF Updating the UI from another thread created within another classC#/WPF 从另一个类中创建的另一个线程更新 UI
【发布时间】:2014-04-30 20:31:08
【问题描述】:

我来自嵌入式 C 背景,我正在开发我的第一个 C# 应用程序,但我在这方面遇到了困难,我的研究没有成功,所以我想我会在这里问。

简单的应用程序,到目前为止。我有一个 MainWindow,它在一堆其他东西中,在单击按钮时启动一个 TCPClient 线程:

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 中:

   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 发送一条消息,例如“客户端线程正在连接...”、“客户端线程已连接...”并让它显示在网络列表框。

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

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

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

再往下,有人提倡数据共享。同样,这对我来说似乎不太理想。

我读过的其他文章似乎已经过时了。

所以,我欢迎任何有关如何解决此问题的解释。可能是我只是在语法上被挂断了,但我怀疑,虽然我认为我对委托、lambdas 等基本很清楚,但我可能对到底需要完成什么感到困惑。

如果你能在这个具体的例子中通过一些解释来展示它是如何完成的,我将不胜感激。

也许还有一些对我来说有点模糊的具体问题:

1) 我的工作任务可以自行访问还是必须通过 UI 的 Dispatcher 提供?

2) UI 是否应该提供一个执行调度的委托,或者调度是否应该在工作任务中编码,引用 UI 调度程序?

非常感谢。

【问题讨论】:

  • 只是关于风格的说明。使用实现 IDisposable 的对象时,通常最好将其包装在“使用”语句中。 TcpClient 和 NetworkStream 都实现了 IDisposable。这是link 解释“使用”语句。用 C# 玩得开心!

标签: c# wpf multithreading user-interface dispatcher


【解决方案1】:

关于提供样品的问题,如果有工人阶级,比如...

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,它接受一个字符串。然后应该实现视图模型,使其不关心消息来自哪个线程。这是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 发布所有内容

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

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

最后,在 UI 上,它是这样呈现的……

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

...

所以回顾一下您的问题:工作线程可以访问它,但他们应该不必必须处理同步 UI。该责任是VM的。并且 VM 应该是线程不可知的,并通过调度程序或同步上下文或其他方法同步 UI。

如果您正在操作作为绑定主题的集合(例如,ObservableCollection),则通过 Dispatcher 进行调度是合适的;否则 SynchronizationContext 是合适的(它更轻量级)。

【讨论】:

  • 虽然我认识到 MVVM 是一种有用且经过深思熟虑的模式,但对于这个应用程序来说似乎有点矫枉过正。之所以提出这个问题,是因为确实需要第二个线程。我还没有真正深入研究 observables 和绑定所有东西,所以我将更多地研究这些主题。谢谢。
  • @user1680587,无需感谢。请阅读stackoverflow.com/help/someone-answers
【解决方案2】:

只需添加委托并传递对主窗体的引用

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 delegate void updateNetworkListBoxDelegate(string message);
    public void updateNetworkListBox(string message)
    {
        if(this.invokeRequired())
        {
            this.invoke(new updateNetworkListBoxDelegate(updateNetworkListBox), message);
        }
        else
        {
            networkListBox.Items.Add(message);
        }
    }
}

在 TCPConnection 中添加一个带有 MainWindow 实例的构造函数

public class TCPConnection
{
    //add member to hold instance
    private _mainWindow;
    //add constructor taking instance
    public TCPConnection(MainWindow  mw)
    {
        _mainWindow = mw;
    }
    public void Connect(string server, ReportDelegate reportDelegate)
    {
        this.server = server;
        clientThread = new Thread(() => Client(this.server));
        clientThread.Start();
        //reportDelegate("Started client thread...");
        //call the method on the UI thread
        _mainWindow.updateNetworkListBox("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();
            //call the method ont he ui thread
            _mainWindow.updateNetworkListBox("DONE!!")
        }
    }
}

【讨论】:

    猜你喜欢
    • 2020-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多