从异步操作调用时,如何从STA线程更新Window控件? [英] How can I update my Window controls from an STA Thread when calling from an async operation?

查看:70
本文介绍了从异步操作调用时,如何从STA线程更新Window控件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My server application is a WPF project which uses asynchronous callbacks to handle client requests and to send responses back to the client.

服务器将基于从客户端接收的数据来更新其数据库,并根据该数据的性质,通过其自己的UI推送警报以反映新警报.

The server is to update its database based on the data received from the client and, depending on the nature of that data, push an alert through its own UI to reflect the new alert.

当需要更新UI时,我收到一条错误消息,说调用线程必须是STA线程或类似的东西.

When it comes time to update the UI, I'm getting an error saying that the calling thread must be an STA thread or some such thing.

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

How can I make sure that the thread that's trying to update the UI is properly set up to do it without causing errors?

有关我的所有代码和有关代码功能的注释,请参见下文.

See below for all of my code and commentry about what the code is doing.

ClientHelper类是用于客户端请求的包装器.

The ClientHelper class is a wrapper for client requests.

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.

FooServer

The server uses a TcpListener that's running on its own thread so as to avoid locking the 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();
    }  

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

The server keeps track of its connected clients by maintaining a list of them.

    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是所有这些操作的起点.这是服务器所做的最后一次读取.

After it reads the client's data, the server executes functions that manipulate the data and forward the alert to the UI. HandleClientData is where all of this starts. Its the last read that the server does.

    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读取的列表中,该列表下方将警报添加到数据库中.

EvaluateFooData checks the client data against acceptable norms and adds any deviation to a list that gets read by AddAlert below which adds the alerts to the database.

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:

Using the EventHandler which is registered on the UI, the server pushes an alert to the 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通过更改在Window初始化期间创建的按钮的样式来更新UI,以便这些按钮现在指示该Foo有问题.基本概念是绿色=好,红色=坏.

Server_RaiseAlert updates the UI by changing the style of buttons that were created during initialization of the Window so that those buttons now indicate a problem with that Foo. The basic concept is green = good, red = bad.

推荐答案

执行在Dispatcher Action中操纵UI元素的所有操作:

Do everything that manipulates UI elements inside the Dispatcher Action:

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的来源.

Note however that from your question it isn't clear where FooId comes from.

这篇关于从异步操作调用时,如何从STA线程更新Window控件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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