从异步操作调用时,如何从STA线程更新我的Window控件?

时间:2017-06-01 14:51:40

标签: c# wpf multithreading

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

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

当需要更新UI时,我收到一条错误,说调用线程必须是STA线程或某些东西。

如何确保尝试更新UI的线程设置正确,而不会导致错误?

有关代码正在执行的操作,请参阅下面的所有代码和注释。

客户助手

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

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,以便这些按钮现在指示该Foo的问题。基本概念是绿色=好,红色=坏。

1 个答案:

答案 0 :(得分:1)

在Dispatcher Action中执行操作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来自何处。