从异步操作调用时,如何从sta线程更新窗口控件



我的服务器应用程序是一个WPF项目,它使用异步回调来处理客户端请求并将响应发送回客户端。

服务器将根据从客户端接收到的数据更新其数据库,并根据数据的性质,通过其自己的UI推动警报以反映新的警报。

是时候更新UI时会遇到一个错误,说该通话线程必须是sta线程或某些此类内容。

如何确保试图更新UI的线程可以正确设置以执行此操作而不会引起错误?

有关我的所有代码和评论,请参见下文。

客户助手

客户端汉牌类是客户端请求的包装器。

public class ClientHelper
{
    private readonly TcpClient _Client;
    private readonly byte[] _Buffer;
    public Client(TcpClient client)
    {
        _Client = client;
        int BufferSize = _Client.ReceiveBufferSize;
        _Buffer = new byte[BufferSize];
    }
    public TcpClient TcpClient
    {
        get { return _Client; }
    }
    public byte[] Buffer
    {
        get { return _Buffer; }
    }
    public NetworkStream NetworkStream
    {
        get { return TcpClient.GetStream(); }
    }
}

fooserver

服务器使用在自己的线程上运行的tcplistener,以免锁定UI。

public class FooServer
{
    private TcpListener Svr;
    public void StartServer()
    {
        Thread ListenerThread = new Thread(new ThreadStart(() =>
        {
            Svr = new TcpListener(IPAddress.Parse("127.0.0.1"), 13000);
            Svr.Start();
            Svr.BeginAcceptTcpClient(AcceptClientCallback, null);
        }));
        ListenerThread.SetApartmentState(ApartmentState.STA);
        ListenerThread.IsBackground = true;
        ListenerThread.Start();
    }  

服务器通过维护它们的列表来跟踪其连接的客户端。

    private List<Client> ConnectedClients = new List<Client>();
    private void AcceptClientCallback(IAsyncResult result)
    {
        TcpClient Client;
        try
        {
            Client = Svr.EndAcceptTcpClient(result);
        }
        catch (Exception ex)
        {
            OnError(Svr, ex);
            //Svr.Stop();
            return;
        }
        Svr.BeginAcceptTcpClient(AcceptClientCallback, null);
        ClientHelper _Client = new ClientHelper(Client);
        ConnectedClients.Add(_Client);
        NetworkStream Stream = _Client.NetworkStream;
        Stream.BeginRead(_Client.Buffer, 0, _Client.Buffer.Length, ReadCallback, _Client);
    }

读取客户端数据后,服务器执行操纵数据并将警报转发到UI的功能。HandleClientData是所有这些开始的地方。这是服务器所做的最后一个读物。

    private void ReadCallback(IAsyncResult result)
    {
        ClientHelper Client = result.AsyncState as ClientHelper;
        if (Client != null)
        {
            NetworkStream Stream = Client.NetworkStream;
            int Read;
            try
            {
                Read = Stream.EndRead(result);
            }
            catch (Exception ex)
            {
                OnError(Client, ex);
                return;
            }
            if (Read == 0)
            {
                Client.TcpClient.Close();
                ConnectedClients.Remove(Client);
                return;
            }
            byte[] Data = new byte[Read];
            Buffer.BlockCopy(Client.Buffer, 0, Data, 0, Read); // copy read data to the client's buffer
            Stream.BeginRead(Client.Buffer, 0, Read, ReadCallback, Client); // read data
            HandleClientData(Stream, Encoding.ASCII.GetString(Client.Buffer, 0, Data.Length));
        }
    }
    private void HandleClientData(NetworkStream stream, string data)
    {
        byte[] value = null;
        try
        {
            string[] Data = data.Split(',');
            if (String.Equals(Data[0], "GetAllFoo"))
            {
                value = Encoding.ASCII.GetBytes(GetFoo());
            }
            else if (String.Equals(Data[0], "GetAFoo"))
            {
                int FooId;
                Int32.TryParse(Data[1], out FooId);
                value = Encoding.ASCII.GetBytes(GetFoo(FooId));
            }
            else
            {
                // Update the Foo in the database to reflect the latest state of every component.
                UpdateFoo(Data);
                // evaluate the data for a fault and raise an alert if there's something wrong.
                if (!EvaluateFooData(Data[1]))
                {
                    AddAlert();
                }
                value = Encoding.ASCII.GetBytes("SUCCESS,The Foo was successfully updated.|");
            }
            stream.Write(value, 0, value.Length);
        }
        catch (Exception ex)
        {
            string Error = String.Format("ERR,{0}", ex.Message);
            byte[] ErrorBytes = Encoding.ASCII.GetBytes(Error);
            stream.Write(ErrorBytes, 0, ErrorBytes.Length);
            return;
        }
    }
}

EvaluateFooData根据可接受的规范检查客户端数据,并将任何偏差添加到下面AddAlert读取的列表中,该列表将警报添加到数据库中。

public void AddAlert()
{
    ApplicationDbContext Context = new ApplicationDbContext();
    foreach (Alert Alert in Alerts)
    {
        Context.Alerts.Add(Alert);
    }
    Context.SaveChanges();
    OnRaiseAlert();
}
public event EventHandler RaiseAlert;
protected virtual void OnRaiseAlert()
{
    RaiseAlert?.Invoke(this, null);
}

使用在UI上注册的EventHandler,服务器将警报推向UI:

public MainWindow()
{
    InitializeComponent();
    Server.RaiseAlert += Server_RaiseAlert;
}
private void Server_RaiseAlert(object sender, EventArgs e)
{
    ApplicationDbContext Context = new ApplicationDbContext();
    var Alerts = Context.Alerts.Where(x => x.IsResolved == false).ToList();
    StackPanel FooStackPanel = new StackPanel();
    spFoo.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { FooStackPanel = spFoo; }));
    if (Alerts != null && Alerts.Count >= 1)
    {
        foreach (Alert Alert in Alerts)
        {
            Button Btn = (Button)FooStackPanel.Children[FooId];
            Btn.Style = FindResource("FooAlertIcon") as Style;
        }
    }
}

Server_RaiseAlert通过更新UI来更新UI,通过更改窗口初始化期间创建的按钮的样式,以便这些按钮现在表明该FOO的问题。基本概念是绿色=好,红色=坏。

执行操纵UI元素在调度程序操作中的所有操作:

private void Server_RaiseAlert(object sender, EventArgs e)
{
    var context = new ApplicationDbContext();
    var alerts = context.Alerts.Where(x => x.IsResolved == false).ToList();
    if (alerts.Count > 0)
    {
        spFoo.Dispatcher.Invoke(new Action(() =>
        {
            foreach (var alert in alerts)
            {
                var button = (Button)spFoo.Children[FooId];
                button.Style = FindResource("FooAlertIcon") as Style;
            }
        }));
    }
}

请注意,从您的问题中,尚不清楚FooId来自何处。

最新更新