UI 线程阻止我的窗口,如何更改?



这是我的代码:

public void BroadcastTheConnection()
{
try
{
//............avoid unnecessary codes 
while (ServerRunning)
{
s = myList.AcceptSocket();// blocking the content until client request accomplished 

displayText.AppendText("nConnected");
Thread tcpHandlerThread = new Thread(tcpHandler);
tcpHandlerThread.Name = "tcpHandler";
tcpHandlerThread.Start();
}
}catch (Exception ex)
{
displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
}
}

当我尝试连接多个客户端时,此代码可以完美运行。 当我在广播连接后尝试移动我的表单时,它不起作用。我知道这是线程问题,但是如何避免这种麻烦?

这是我的按钮:

private void bBroadcast_Click(object sender, EventArgs e)
{
BroadcastTheConnection();          
}

我需要使用锁定语句吗?还是委托?有什么想法吗?然后怎么做?

问题是BroadcastTheConnection()是从 UI 线程本身调用的。 由于它具有while (ServerRunning) {}构造,因此 UI 线程将在代码上旋转,直到ServerRunning为 false。

有几种方法可以实现相同的修复:从 UI 线程中获取服务器代码。这些中的每一个都有其权衡。

  • BroadcastTheConnection()用作长时间运行的任务(不推荐)
  • 站起来一个线程,其中BroadcastTheConnection()是主要方法。
  • 使用异步套接字调用。 (太复杂了,无法快速回答)

长时间运行的任务

Task.Factory.StartNew(BroadcastTheConnection,
TaskCreationOptions.LongRunning);

这既快速又简单,但您不希望太多长时间运行的任务,因为它们可能会长时间占用任务线程池中的线程。

专用线程

Thread connectionThread = new Thread(BroadcastTheConnection)
{
Name = "BroadcaseTheConnection Thread",
IsBackground = true
};
connectionThread.Start();

这不使用任务线程池中的任何线程,为您提供一个有助于调试的命名线程,并在您忘记结束应用程序时防止线程保持应用程序运行。

从套接字代码使用 UI

每当需要以任何方式与 UI 交互时,都需要再次将调用放入 UI 线程中。 WinForms 和 WPF 执行相同操作的方式略有不同。

赢形

myControl.BeginInvoke(myControl.Method); // nonblocking
myControl.Invoke(myControl.Method); // blocking

可湿性工作基金会

myControl.Dispatcher.BeginInvoke(myControl.Method); // nonblocking
myControl.Dispatcher.Invoke(myControl.Method); // blocking

请注意,对连续BeginInvoke的调用过多可能会使 UI 线程过载。 对它们进行批处理比连续发出大量请求要好。

使用 Async 和 Await 进行以下更改

private async void bBroadcast_Click(object sender, EventArgs e) //-- Async
{
ipAdrsNew = ipBox.Text;
portNo = Convert.ToInt32(portBox.Text);
await BroadcastTheConnection();           //-- await
}
public Task BroadcastTheConnection()
{
return Task.Run(() =>
{
//---- code for BroadcastTheConnection 
});
}

可以使用 async 和 await 在 C# 中实现异步网络。

试试这个(我也重构了你的代码):

public async Task BroadcastConnectionAsync(IPAddress address, int port)
{
try
{
var listener = new TcpListener(address, port);
ServerRunning = true;
// Start Listeneting at the specified port
listener.Start();
displayText.AppendText("The server is running at port 8001...n");
while (ServerRunning)
{
using (var socket = await listener.AcceptSocketAsync())
{
listOFClientsSocks.Add(socket);
listBox1.DataSource = listOFClientsSocks;
displayText.AppendText("nConnected");
new Thread(tcpHandler)
{
Name = "tcpHandler"
}.Start();
}
}
}
catch (Exception ex)
{
displayText.AppendText("Error----" + Environment.NewLine + ex.StackTrace);
}
}

和您的点击事件处理程序:

private async void bBroadcast_Click(object sender, EventArgs e)
{
var address = IPAddress.Parse(ipBox.Text);
int port = Convert.ToInt32(portBox.Text);
await BroadcastConnectionAsync(address, port);
}

使用Thread与远程端进行侦听和通信的最简单示例:

public class ListenerThread
{
// clients list/queue
Queue<ClientConnection> m_Clients;
// thread used to listen for new connections
Thread m_Thread;
Socket m_Socket;
IPEndPoint m_LocalEndPoint;
volatile bool m_IsListening;
public ListenerThread(int port)
{
// get this machine hostname
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());  
// resolve ip address from hostname
IPAddress ipAddress = ipHostInfo.AddressList[0];  
// create local end point object 
m_LocalEndPoint = new IPEndPoint(ipAddress, port);  
}
void Listen()
{
// reset clients list
m_Clients = new Queue<ClientConnection>();
// initialize socket
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); 
// bind this socket to listen for incomming connections to specified end point
m_Socekt.Bind(localEndPoint);
// start listening with backlog of 1337 connections
m_Socket.Listen(1337);  
// dont forget to dispose after socket was used to "unbind" it
using ( m_Socket )
{
while ( m_IsListening )
{
// while listening just accept connections and start them at another thread
Socket client = m_Socket.Accept();
if ( client != null )
{
m_Clients.Enqueue(new ClientConnection(client));
}
}
}
}
// method used to start the listening server
public void Start()
{
if ( m_Thread == null )
{
m_Thread = new Thread(Listen);
}
m_IsListening = true;
m_Thread.Start();
}
// method used to stop listening server
public void Stop()
{
m_Listening = false;
m_Thread.Join();
while ( m_Clients.Count != 0 )
{
m_Clients.Dequeue().Kill();
}
}
}
// class used to communicate with the client
public class ClientConnection
{
Socket m_Socket; // client socket
Thread m_Thread; // communication thread
volatile bool m_IsCommunicating;
// this should start immediately because of the incomming connection priority
internal ClientConnection(Socket socket)
{
m_Socket = socket;
m_Thread = new Thread(Communicate);
m_Thread.Start();
}
// loop in which you should send/receive data
void Communicate()
{
while ( m_IsCommunicating )
{
// .. do your communication stuff
}
}
// should be only used by ListenerThread to end communication.
internal void Kill()
{
m_IsCommunicating = false;
try
{
m_Thread.Join(5 * 1000);
m_Thread.Abort();
}
catch(Exception ex) { /*...*/ }
}
}

这确实是最简单的示例,因此您应该根据需要对其进行修改。
要将其与您的示例一起使用,只需开始ListenerThread

ListenerThread listener = new ListenerThread(8001);
listener.Start();
displayText.AppendText("The server is running at port 8001...n");

最后一件事是,如果您想调用 UI,我建议使用SynchronizationContext.为了更清楚地说明ListenerThread构造函数调用:

m_Sync = SynchronizationContext.Current;

并制作另一个字段:

SynchronizationContext m_Sync;

然后只需将此上下文作为new ClientConnection(m_Sync, client);传递到ClientConnection构造函数中。

现在您可以使用SynchronizationContext.Post方法,例如:

m_Sync.Post( state => { someUITextElement.AppendText((string)state); }, "hello world");

有一个异步变体AcceptSocket称为BeginAcceptSocket,它异步等待连接并为连接的新套接字启动新线程。您仍然需要等待操作完成,因为您处于while循环中,但是您可以使用这段时间调用Application.DoEvents,这将允许UI更新。

while (ServerRunning)
{
AsyncHandler handler = delegate(asyncResult)
{
//Get the new socket
Socket socket = myList.EndAcceptSocket(asyncResult);
//Marshal UI specific code back to the UI thread
MethodInvoker invoker = delegate()
{
listOFClientsSocks.Add(socket);
listBox1.DataSource = listOFClientsSocks;
displayText.AppendText("nConnected");
};
listBox1.Invoke(invoker);
//Call the handler
tcpHandler();
}
IAsyncResult waitResult = myList.BeginAcceptSocket(handler, null);
//Wait until the async result's wait handle receives a signal
//Use a timeout to referesh the application every 100 milliseconds
while (!waitResult.AsyncWaitHandle.WaitOne(100))
{
Application.DoEvents();
if (!ServerRunning)
{
break;
}
}
}

该解决方案使 UI 响应代码结构的相对更改。但是,我建议您重新考虑使用TcpListener的整个策略。在 UI 线程中侦听 TCP 连接通常不是一个好主意。创建一个专用类,该类在单独的线程中为您执行侦听,并从 UI 代码访问它。

您还应该知道,在上面的代码中,catch块不会处理BeginAcceptSocket使用的匿名委托中的任何内容。我还添加了代码,以便在服务器不再运行时停止侦听。这可能不是必需的,因为在这种情况下BeginAcceptSocket会引发异常。不过,它是一种额外的保障措施。

您也可以使用后台工作者。下面是一个相同的小例子。

private void MainMethod()
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += Bg_DoWork;
bg.RunWorkerCompleted += Bg_RunWorkerCompleted;
}
private void Bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//here do your UI related work like changing the color of label or filling up data in grid etc
}
private void Bg_DoWork(object sender, DoWorkEventArgs e)
{
//Here do your time consuming work without blocking ui thread
}

最新更新