Control.Invoke not as synchronous as I thought
msimpson_at_computer.org
Date: 02/07/05
- Previous message: TyBreaker: "Double Buffering in PictureBox"
- Messages sorted by: [ date ] [ thread ]
Date: 7 Feb 2005 13:05:42 -0800
I've been fighting with multithreading/gui issues for the last couple
of weeks and thought I found the solution in Control.Invoke, but things
still aren't working out as I expected. In order to try to isolate the
problem, I stripped my code down to the minimum possible to see the
problem. Basically what I want is to have a main function that does
the following:
1. Create a form that contains a RichTextBox. When the form opens, it
will create a socket, listen (using BeginAccept) for incoming
connections, and display "Server started on address xxx" on the
RichTextBox.
2. Create a client to connect to the server. When the server sees the
connection, it should display "Client connected" in the textbox.
3. Verify the contents of the textbox (which should be "Server
started... Client connected".
4. Shut everything down.
Because a separate thread is spawned when the server socket sees a
connection, I use Invoke to update the textbox. I thought that by
using Invoke (synchronous) rather than BeginInvoke (asynchronous), the
update to the message box would occur before that thread would continue
processing, but that's not the case. When I verify the contents of the
textbox in step 3, it doesn't contain the "client connected" message
sent by the thread spawned by the connection request.
The results I'm seeing are:
1 [2296] frmServer:frmServer.<<create>> {
2 [2296] IPEndPoint:localHostAddress.<<create>>(hostAddress,
nPortNumber) -> localHostAddress
3 [2296] Socket:m_listenSocket.<<create>>
4 [2296] Socket:m_listenSocket.Bind(localHostAddress)
5 [2296] Socket:m_listenSocket.Listen(10)
6 [2296] beginning invoke of '*** Server started on address
127.0.0.1:13535'
7 [2296] frmServer:frmServer.UpdateHistory('*** Server started on
address 127.0.0.1:13535') {
8 [2296] }
9 [2296] Socket:m_listenSocket.BeginAccept(OnConnectionRequest)
10 [2296] }
11 [2296] Client:client.<<create>> ('test1') {
12 [2296] }
13 [2296] Client:client.ConnectToServer(hostAddress) {
14 [2296] Socket:m_socket.<<create>>
15 [2296] Socket:m_socket.Connect(hostAddress)
16 [2296] [m_socket.Connected] {
17 [2452] Server:m_server.OnConnectionRequest {
18 [2452] Socket:m_listenSocket.EndAccept(ar) -> connectedSocket
19 [2452] beginning invoke of 'client has joined'
20 [2296] }
21 [2296] }
22 [2296] historyBox.Text = *** Server started on address
127.0.0.1:13535
23 [2296] Client:client.Shutdown() {
24 [2296] Socket:m_socket.Shutdown
25 [2296] Socket:m_socket.Close
26 [2296] }
Thread 2296 is the main thread, and 2452 is the thread spawned to
receive the connection. Lines 6 and 7 show the initial Invoke and
immediate updating for the "Server started..." message. Line 19 shows
the Invoke call for the "client connected" message, but I never see the
actual updating function entered. Can anyone clarify my understanding
of exactly what synchronous means with regards to Invoke? And even
more important, is there any way to successfully accomplish what I'm
attempting (safely update a gui control from socket threads and have
the gui updated before I test the contents of the text box in the main
thread)?
Thanks!
Here is the code I used:
[frmServer.cs]
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
namespace SimplyAsync
{
/// <summary>
/// Summary description for frmServer.
/// </summary>
public class frmServer : System.Windows.Forms.Form
{
private System.Windows.Forms.RichTextBox txtHistory;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
protected Socket m_listenSocket;
// For the asynchronous updating of the window
delegate void HistoryCallback(String newMessage, Color messageColor);
HistoryCallback updateHistory;
public frmServer(int nPortNumber)
{
DebugMessage.Print("frmServer:frmServer.<<create>> {");
//
// Required for Windows Form Designer support
//
InitializeComponent();
updateHistory = new HistoryCallback(UpdateHistory);
IPHostEntry hostEntry = Dns.Resolve("localhost");
IPAddress hostAddress = hostEntry.AddressList[0];
DebugMessage.Print("IPEndPoint:localHostAddress.<<create>>(hostAddress,
nPortNumber) -> localHostAddress");
IPEndPoint localHostAddress = new IPEndPoint(hostAddress,
nPortNumber);
DebugMessage.Print("Socket:m_listenSocket.<<create>>");
m_listenSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
try {
DebugMessage.Print("Socket:m_listenSocket.Bind(localHostAddress)");
m_listenSocket.Bind(localHostAddress);
DebugMessage.Print("Socket:m_listenSocket.Listen(10)");
m_listenSocket.Listen(10);
// start accepting incoming connections
String sServerListening = String.Format("Server started on address
{0}:{1}", hostAddress.ToString(), nPortNumber);
String sMessage = "*** " + sServerListening + "\n";
DebugMessage.Print("beginning invoke of '" + sMessage + "'");
if (txtHistory.InvokeRequired) {
Invoke(updateHistory, new Object[] {sMessage, Color.Green});
} else {
updateHistory(sMessage, Color.Green);
}
DebugMessage.Print("Socket:m_listenSocket.BeginAccept(OnConnectionRequest)");
m_listenSocket.BeginAccept(new AsyncCallback(OnConnectionRequest),
m_listenSocket);
} catch (SocketException se) {
if (se.ErrorCode == 10048) {
DebugMessage.Print("Server error: This address and port are
already in use.");
} else {
DebugMessage.Print("Server error: Socket Error " + se.ErrorCode +
" trying to bind server.");
}
} finally {
DebugMessage.Print("}");
}
}
private void OnConnectionRequest(IAsyncResult ar)
{
DebugMessage.Print("Server:m_server.OnConnectionRequest {");
try {
// EndAccept method returns a socket that we get a reference to
DebugMessage.Print("Socket:m_listenSocket.EndAccept(ar) ->
connectedSocket");
Socket connectedSocket = m_listenSocket.EndAccept(ar);
String sMessage = "client has joined";
if (txtHistory.InvokeRequired) {
DebugMessage.Print("beginning invoke of '" + sMessage + "'");
Invoke(updateHistory, new Object[] {sMessage, Color.Green});
} else {
updateHistory(sMessage, Color.Green);
}
// Listen for another connection
DebugMessage.Print("Socket:m_listenSocket.BeginAccept(OnConnectionRequest)");
m_listenSocket.BeginAccept(new
System.AsyncCallback(OnConnectionRequest), m_listenSocket);
} catch (ObjectDisposedException ode) {
// Just to catch those cases where we're shutting down
DebugMessage.Print("Server:server.OnConnectionRequest - handled
ObjectDisposedException");
} catch (Exception e) {
DebugMessage.Print("Server error: " + e.Message);
} finally {
DebugMessage.Print("}");
}
}
void UpdateHistory(String newMessage, Color messageColor)
{
DebugMessage.Print("frmServer:frmServer.UpdateHistory('" +
newMessage + "') {");
txtHistory.SelectionColor = messageColor;
txtHistory.AppendText(newMessage);
DebugMessage.Print("}");
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.txtHistory = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
//
// txtHistory
//
this.txtHistory.Location = new System.Drawing.Point(0, 0);
this.txtHistory.Name = "txtHistory";
this.txtHistory.ReadOnly = true;
this.txtHistory.Size = new System.Drawing.Size(288, 272);
this.txtHistory.TabIndex = 0;
this.txtHistory.Text = "";
//
// frmServer
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.txtHistory);
this.Name = "frmServer";
this.Text = "frmServer";
this.ResumeLayout(false);
}
#endregion
}
}
[SimpleAsync.cs]
using System;
using System.Collections;
using System.Net;
using System.Windows.Forms;
namespace SimplyAsync
{
/// <summary>
/// Summary description for SimpleAsync.
/// </summary>
public class SimpleAsync
{
public static void Main(string[] args)
{
// Create and display the server form
frmServer server = new frmServer(13535);
server.Show();
// Create the client and connect to the server
Client client = new Client("test1");
IPEndPoint hostAddress = new
IPEndPoint(Dns.Resolve("localhost").AddressList[0], 13535);
client.ConnectToServer(hostAddress);
// Display the contents of the text box
RichTextBox historyBox = null;
IEnumerator enumControls = server.Controls.GetEnumerator();
while (enumControls.MoveNext() != false) {
if (enumControls.Current is RichTextBox) {
historyBox = (RichTextBox)enumControls.Current;
break;
}
}
DebugMessage.Print("historyBox.Text = " + historyBox.Text);
// Shut everything down
client.Shutdown();
server.Close();
// Wait for user input
Console.ReadLine();
}
}
}
- Previous message: TyBreaker: "Double Buffering in PictureBox"
- Messages sorted by: [ date ] [ thread ]
Relevant Pages
|