我的服务器应用程序是一个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(); }
}
}
服务器使用在自己的线程上运行的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的问题。基本概念是绿色=好,红色=坏。
答案 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
来自何处。