2017-04-10 34 views
-1

這裏是我的代碼: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();   
     } 

我需要用鎖語句?或代表?有任何想法嗎?那麼如何?

+0

您應該考慮爲網絡通信目的創建專用的「線程」。那麼如果你想從通信線程傳輸數據到UI線程中,你應該使用一些同步上下文。 –

+0

你需要把broadcasttheconnection到一個線程 – BugFinder

+0

@BugFinder - 如果你不介意的話,請你給我一個例子... –

回答

1

的問題是,BroadcastTheConnection()正在從UI線程本身調用。由於它具有while (ServerRunning) {}構造,UI線程將旋轉您的代碼直到ServerRunning爲假。

有幾種方法可以實現相同的修復:從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在做同樣的事情方面略有不同。

的WinForms

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

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

WPF

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

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

被警告,太多調用BeginInvoke一排可以重載UI線程。批量處理比連續處理大量請求更好。

+0

爲什麼不使用異步並等待?這很容易。檢查我的答案。 –

+0

在專用線程使用中,匿名方法正確嗎? –

+0

@KfirGuy,它與'BroadcastTheConnection()'代碼長時間運行有關。此外,更容易確定何時有多個這種情況同時發生。這就是爲什麼我會提倡一個專門的線程。 –

0

讓使用異步更改下方伺機

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 
    }); 
} 
+0

你爲什麼要直接管理這些任務?只需使用異步並等待。 –

+0

所以這個任務是錯誤的。在C#中,異步編程的正確方法是使用異步和等待。查看我的答案,看看它比使用線程或任務更容易。 –

1

您可以使用async and 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); 
} 
+0

謝謝,我也會試試這個... :) –

+0

異步等待是最好的,建議選擇,但有一個問題,因爲事件是無效返回類型,Async方法也應該是void返回不是Task。另外啓動另一個線程,在Async方法內更新Ui控制並不是很好的操作 –

0

您可以使用後臺工作也。下面是一個相同的小例子。

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 
     } 
0

使用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; 

然後只是通過這一背景下進入ClientConnection構造函數new ClientConnection(m_Sync, client);

現在你可以使用SynchronizationContext.Post方法例如。 :

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

有的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將引發異常。它雖然作爲一個額外的保障。