【问题标题】:C# Property and PropertyChanged Threading FailC# 属性和 PropertyChanged 线程失败
【发布时间】:2014-05-09 19:05:50
【问题描述】:

最后,我收到线程无法访问,因为它由另一个线程拥有。但是,这与 UI 无关(直接)。仅当我尝试设置对象的属性而不是成员本身时才会引发异常。如果我直接设置成员,它工作正常,但我失去了我的 PropertyChanged 事件。 请,如果我过于复杂了,请告诉我!我想不出其他方式将属性返回到 MainWindow。

您可以快进到下面的第 3 类查看结果。在最高级别,我涉及三个类:MainWindow、OCXComms 和 ZServer。当按下监听按钮时,它会调用 OCXComms 中的启动方法并负责创建 ZServer 实例,如下所示:

第 1 类

public partial class MainWindow : Window
{
    private OCUComms comms;

        ......

    private void roxListenBtn_Click(object sender, RoutedEventArgs e)
    {
        //Debugger Shows Main Thread
        comms.StartOutput();
    }

   void comms_IsClientConnectedChanged(object sender, PropertyChangedEventArgs e)
    {
        connectionStatusLbl.Content = "Connected - No Control";
        connectionStatusLbl.Foreground = myWarningBrush;

        roverControlBtn.IsEnabled = true;
        manualControlRB.IsEnabled = true;
    }

        ......
}

接下来,通过 StartOutput 在“MainThread”上构造 ZServer 对象。这很好用。

第 2 类

public sealed class OCXComms : INotifyPropertyChanged
{

        private static OCXComms instance;
        private static readonly object instanceSync = new object();

        private Boolean _isClientConnected = false;
        private bool isEnabled;

          .....

     public static OCXComms Instance
    {
        get
        {
            if (instance == null)
            {
                lock (instanceSync)
                {
                    if (instance == null)
                        instance = new OCXComms();
                }
            }

            return instance;
        }
    }

    private OCXComms()
    {
        isEnabled = false;
        _isControllerEnabled = false;
        _isKeyboardEnabled = false;

        worker = new System.Timers.Timer();

        worker.Elapsed += new ElapsedEventHandler(worker_Elapsed);

        Properties.Settings.Default.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Default_PropertyChanged);
    }

     public event PropertyChangedEventHandler PropertyChanged;


    private void OnClientConnectedChanged()
    {
        if (PropertyChanged != null)

            PropertyChanged(this, new PropertyChangedEventArgs("IsClientConnected"));
    }

    void server_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.IsClientConnected = server.getIsClientConnected();
    } 

        public void StartOutput()
    {
            if (isEnabled)
                return;

            Console.WriteLine("ZServer started on: " + Thread.CurrentThread.Name);

            //Debugger Shows Main Thread
            this.server = new ZServer(Properties.Settings.Default.ListeningPort);
            this.server.PropertyChanged += server_PropertyChanged;
            this.server.Start();

            IsEnabled = true;

    }
}

这就是问题所在。请注意两种不同的尝试(仅在此处显示,实际上并未在语法中显示)。如果没有断点,客户端和服务器就会挂起,如果设置了断点,则第一个更改异常会通知我一个线程正在尝试访问另一个线程拥有的信息。我很清楚 ZServer 是在“MainThread”上创建的,此时我在侦听器线程上。我毫不怀疑这是问题所在。但是,我不确定如何进行。我的大部分研究都指向 Dispatcher,但我不在它的主窗口中……“我深三类;)”我尝试过锁定、监控、新线程,但都没有成功。任何建议将不胜感激!

第 3 类

public class ZServer
{
    private Thread listenThread;
    private Boolean _isClientConnected = false;

    public ZServer(String sIP, int iPort)
    {
        InitializeServer(sIP, iPort);
    }

       private void InitializeServer(String sIP, int iPort)
    {
        this.tcpListener = new TcpListener(IPAddress.Parse(sIP), iPort);
        this.listenThread = new Thread(new ThreadStart(ListenForClients_DoWork));
        this.listenThread.Name = "Listen Thread";

    }

            public Boolean IsClientConnected
    {
        get 
        {
        return _isClientConnected;
        }

        private set 
        {
            _isClientConnected = value;
            OnClientConnectedChanged();
        }
    }

    private void OnClientConnectedChanged()
    {
        if (PropertyChanged != null)

            PropertyChanged(this, new PropertyChangedEventArgs("IsClientConnected"));
    }


     private void ListenForClients_DoWork()
    {
        // Start the Server Listening for Clients to connect while blocking.
        this.tcpListener.Start();

        while (!tokenSource.Token.IsCancellationRequested)
        {
            try
            {

                TcpClient client = this.tcpListener.AcceptTcpClient();

                this._isClientConnected = true;  //<== This Works
                this.IsClientConnected = true; //This causes client and server to hang. Possible deadlock?
                ........
            }
            catch (SocketException socketEx)
            {
                Console.WriteLine("Socket Closed from Another Thread: " + socketEx.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine("ZServer: unhandled exception in ListenForClients: " + ex.Message);
            }
        }
        Console.WriteLine("ListenForClients exiting...");
    }

    public Boolean getIsClientConnected()
    {
        lock (_clientConnectedLock)
        {
            return _isClientConnected;
        }
    }   

}

【问题讨论】:

    标签: c# multithreading properties locking polymorphism


    【解决方案1】:

    这里的问题很简单,您在一个线程上创建一个表单,使该线程成为该表单的 UI 线程。但是,在服务器中,您启动一​​个新线程(它不拥有该表单),当客户端连接时会引发一个事件,并通过处理程序将该事件传播到comms_IsClientConnectedChanged。但是,该传播仍在该工作线程上,而不是 UI 线程上。

    因此,当该处理程序代码调用 connectionStatusLbl.Content 时,它会通过非 UI 拥有线程调用 UI 控件。这是一个禁忌。

    在访问表单或其任何后代控件时,在 comms_IsClientConnectedChanged 内使用 .InvokeRequired.Invoke()。这将解决您的问题。

    【讨论】:

    • 我永远不会看那里。感谢您的帮助!我要瞪眼了。
    猜你喜欢
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    相关资源
    最近更新 更多