c# / WPF从另一个类中创建的另一个线程更新UI



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

到目前为止还很简单。我有一个主窗口,在一堆其他的东西,启动一个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,如"客户端线程连接…","客户端线程连接…",并有它显示在networkListbox。

在Connect()方法中,我能够通过使用委托来做到这一点,但这显然不会在新线程中工作,因为一个人不能直接从另一个线程访问UI控件。

我读了很多关于这方面的文章,我知道我可能想使用Dispatcher来完成这个任务。但是,我看到的几乎所有示例都在当前类中创建了一个新线程,例如,向Dispatcher.Invoke()传递了一个匿名方法。

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

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

我看过的其他文章似乎都过时了。

所以,我欢迎任何关于如何做到这一点的解释。这可能是我只是在语法上被挂断了,但我怀疑,尽管我认为我对委托、lambda等都很清楚,但我可能对到底需要做什么感到挂断了。

如果你能在这个具体的例子中给出一些解释,我将非常感激。

可能还有一些我不太清楚的具体问题:

1)我的工作任务可以访问它自己还是必须与UI的Dispatcher提供?

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

对于您关于提供示例的问题,如果有一个worker类,如…

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线程上同步的责任不属于工作线程。worker不应该关心,类似地,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是合适的(它更轻量级)。

只需添加委托并传递一个引用到主表单

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!!")
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新