Reputation: 6245
ALL,
Have the following problem. First, here is my code:
class InternetConnector
{
private static ManualResetEvent receiveDone = new ManualResetEvent( false );
public static ManualResetEvent processingDone = new ManualResetEvent( false );
public static ConcurrentQueue<string> messages = new ConcurrentQueue<string>();
public static bool Connected;
public bool ReceiveMessage(Action<Socket> successHandler, Action<Exception> errorHandler)
{
bool receive = false;
ClientStateObject obj = new ClientStateObject();
obj.server = client;
var connectionData = new ConnectionData
{
ErrorHandler = errorHandler,
SuccessHandler = successHandler,
Socket = client,
clientObj = obj
};
if (Connected)
{
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
receive = true;
receiveDone.WaitOne();
}
return receive;
}
private static void ReceiveCallback(IAsyncResult ar)
{
ConnectionData connectionData = new ConnectionData();
bool complete = false;
try
{
connectionData = (ConnectionData)ar.AsyncState;
Socket client = connectionData.Socket;
int num = client.EndReceive(ar);
{
connectionData.clientObj.stringBuffer.Append(Encoding.ASCII.GetString(connectionData.clientObj.buffer, 0, num));
string response = connectionData.clientObj.stringBuffer.ToString();
if (response.EndsWith("&"))
complete = true;
string[] msgs = response.Split('&');
int j = 0;
if (!complete)
j++;
for (int i = 0; i < msgs.Count() - j; i++)
{
string sts = msgs[i];
if (i == 0 && receivingMessage != String.Empty)
{
sts = receivingMessage + sts;
messages.Enqueue(sts + "&" );
receivingMessage = String.Empty;
}
else
messages.Enqueue(sts + "&");
}
if (!complete)
receivingMessage += msgs[msgs.Count() - 1];
else
receivingMessage = String.Empty;
receiveDone.Set();
if (connectionData.SuccessHandler != null)
{
connectionData.SuccessHandler(client);
processingDone.WaitOne();
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
}
}
}
catch (Exception e)
{
if (connectionData.ErrorHandler != null)
connectionData.ErrorHandler(e);
}
}
}
And here is the success handler:
public partial class Form1 : Form
{
private void AsyncSuccessHandler(Socket socket)
{
if (InvokeRequired)
{
BeginInvoke(new Action( () => AsyncSuccessHandler( socket ) ));
return;
}
if (InternetConnector.messages.Count() == 0)
{
status.Text = "Signals Receiver: Connected";
status.ForeColor = Color.Green;
startStop.Text = "Stop";
isRunning = true;
create.Enabled = true;
SetGUIColorsAndValues();
client.SendMessageToConnector("First Signal Pass4&");
client.ReceiveMessage(AsyncSuccessHandler, AsyncErrorHandler);
}
else
{
SetGUIColorsAndValues();
GUIChangeOnConnection();
InternetConnector.processingDone.Set();
}
}
private void GUIChangeOnConnection()
{
for( int i = 0; i < InternetConnector.messages.Count; i++ )
{
string message;
InternetConnector.messages.TryDequeu( out message );
// process message
}
}
}
Problem: I need to continuosly read the data from the server and display them in the GUI. I also need the GUI to be responsive to i.e. button clicks. I also need to start n read iteration when n-1 read iteration has been finished processing and the concurrentqueue object in my code is empty.
However going thru the code in debugger I can see that BeginReceive() call in the reading callback does not wait till the end of success handler and the queue is not empty.
What am I missing? Is there a better way to achieve this?
I am also aware of SignalR library but at this pont I don't want to use any 3rd party library.
Any help in this matter appreciated.
Thank you.
[EDIT]
Do I understand Amit's reply correctly:
class InternetConnector
{
private static ManualResetEvent processingDone = new ManualResetEvent( false );
private static void ReceiveCallback(IAsyncResult ar)
{
ConnectionData connectionData = new ConnectionData();
bool complete = false;
try
{
connectionData = (ConnectionData)ar.AsyncState;
Socket client = connectionData.Socket;
int num = client.EndReceive(ar);
{
connectionData.clientObj.stringBuffer.Append(Encoding.ASCII.GetString(connectionData.clientObj.buffer, 0, num));
string response = connectionData.clientObj.stringBuffer.ToString();
if (response.EndsWith("&"))
complete = true;
string[] msgs = response.Split('&');
int j = 0;
if (!complete)
j++;
for (int i = 0; i < msgs.Count() - j; i++)
{
string sts = msgs[i];
if (i == 0 && receivingMessage != String.Empty)
{
sts = receivingMessage + sts;
messages.Enqueue(sts + "&" );
receivingMessage = String.Empty;
}
else
messages.Enqueue(sts + "&");
}
if (!complete)
receivingMessage += msgs[msgs.Count() - 1];
else
receivingMessage = String.Empty;
receiveDone.Set();
if (connectionData.SuccessHandler != null)
{
processingDone.WaitOne();
connectionData.SuccessHandler(client);
processingDone.Set();
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
}
}
}
catch (Exception e)
{
if (connectionData.ErrorHandler != null)
connectionData.ErrorHandler(e);
}
}
}
?
[/EDIT] [EDIT 2] Please see updated code. When I set a breakpoint at the very end of the GUIChangeOnConnection() - line with the "}" - I see that the queue does have some items. I will try to change ManualResetEvent, in the meantime. [/EDIT 2]
Upvotes: 0
Views: 1324
Reputation: 2726
If I have understood the problem correctly, you are referring to a problem at the following point of code:
if (connectionData.SuccessHandler != null)
{
connectionData.SuccessHandler(client);
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
}
If your intention is that you hit server only when you have finished processing previous data then the SuccessHandler
must be blocking (as is obvious from your problem statement as well).
Now take a look at the following lines in your SuccessHandler
if (InvokeRequired)
{
BeginInvoke(new Action( () => AsyncSuccessHandler( socket ) ));
return;
}
The above code defers the execution of your SuccessHandler
so that the UI remains responsive. Or in other words, your SuccessHandler
is not blocking.
Since you are updating GUI from the SuccessHandler
, it is also important to run it from UI thread (which it is correctly doing right now).
So in order to make the SuccessHandler
blocking, you can make the following code itself run on UI thread using InvokeRequired
mechanism
if (connectionData.SuccessHandler != null)
{
connectionData.SuccessHandler(client);
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
}
Since client.BeginReceive
is async it will not block the UI thread and you anyway wanted to run SuccessHandler
on UI thread..
If you don't have access to the Form in ReceiveCallback
, you can use a waithandle to block call to client.BeginReceive
till SuccessHandler
has finished.
private static AutoResetEvent _processingDone = new AutoResetEvent( false );
//Introduce an internal property ProcessingDone to access _processingDone
//This should be in a class which you can access both from 'Form1' and the class containing ReceiveCallBack
//In ReceiveCallBack
if (connectionData.SuccessHandler != null)
{
connectionData.SuccessHandler(client);
ProcessingDone.WaitOne();
client.BeginReceive(connectionData.clientObj.buffer, 0, ClientStateObject.bufSize, 0, new AsyncCallback(ReceiveCallback), connectionData);
}
ProcessingDone.Set() must be done from within SuccessHandler
at a point where you can determine that SuccessHandler
has finished and client.BeginReceive
needs to be invoked.
This is internal to your logic. From the code you have shared, it is difficult to pin point the place where you need to put ProcessingDone.Set().
Upvotes: 1