Returning object from AsyncCallback called inside a threaded Infinite loop

J

Jim P.

I'm having trouble returning an object from an AsyncCallback called inside a
threaded infinite loop.

I'm working on a Peer2Peer app that uses an AsyncCallback to rerieve the
data from the remote peer. I have no problem connecting the peers and
streaming Network Streams. When the incoming data is finished recieving, I
act upon it. This works great as long as all of the code is inside my form.

I want to build the networking code into a seperate assembly (to be used by
a server and client apps). It works fine but I can't return the value back
to the Main Form. How can I pass back the retrieved data through the
following steps?? Or, any other strategies on how to tackle this problem?

The Current Process
I start the listener inside a thread and begin an infinite loop to check for
a client connection. When a client connects I call a method that starts the
..BeginRead on the Network Stream. The .BeginRead uses an AsyncCallback
delegate called 'OnReadComplete' and passes a StateObject with the connected
socket.. Inside OnReadComplete, I transfer the callback object to a new
StateObject. I recieve data in 256 byte chunks until the bytes recieved
equals 0. Then I act on that data.

Some example code:

** State Object
----------------
public class StateObject
{
public System.Net.Sockets.Socket socket;
public byte[] buffer = new byte[256];
public NetworkStream networkStream;

public AsyncCallback callbackRead;
}

** Infinite loop used after starting listener
------------------------------------------
// keep listening until you recieve a connection
for (;;)
{
// if client connects, accept the connection
// and return a new socket named socketForClient
// while tcpListener keeps listening
Socket socketForClient = tcpListener.AcceptSocket();
if (socketForClient.Connected)
{
StateObject handler = new StateObject();
handler.socket = socketForClient;
handler.networkStream = new NetworkStream(socketForClient);
handler.callbackRead = new AsyncCallback(this.OnReadComplete);
StartRead(handler);
}
}

** StartRead
-------------
public void StartRead(StateObject handler)
{
handler.networkStream.BeginRead(
handler.buffer, 0, handler.buffer.Length, handler.callbackRead, handler);
}

** OnReadComplete
----------------------
private void OnReadComplete( IAsyncResult ar )
{
// Get the socket
StateObject newSocket = (StateObject)ar.AsyncState;
Socket client = newSocket.socket;

// Get the networkStream
NetworkStream readStream = new NetworkStream(newSocket.socket);

int bytesRead = readStream.EndRead(ar);

// If there is data left to be retieved
if( bytesRead > 0 )
{
string s = System.Text.Encoding.ASCII.GetString( newSocket.buffer, 0,
bytesRead );
readStream.BeginRead( newSocket.buffer, 0, newSocket.buffer.Length,
newSocket.callbackRead, newSocket);
// Store message into string.
message += s;
}
// The incoming stream has finished
else
{
// Act on Incoming Message
actOn(message);

// Close out all objects
newSocket.networkStream.Close();
newSocket.socket.Close();
newSocket.networkStream = null;
newSocket.socket = null;
message = null;
}
}


Thanks,
Jim
 
J

J.Marsch

Jim:

I'm not entirely sure that I understand the question. Let me throw what I
picked up back at you, and you can maybe correct me if I'm wrong:
I assume that the code that you posted all works, except that you have a
scoping issue where you cannot inform the main form that something has been
acted upon because inside your OnReadComplete method, the main form (or the
form that you want to notify) is not in scope. Is that correct?

Assuming that's right, you could could always make a reference to the main
form (or better yet, some kind of broker object) available to the thread
code, which would, in turn, add the reference to your state object:
(warning, this is off the cuff, might need adjustment to compile)

So, what could happen would be that you have an "observer" class that sits
between the form(s) and the state class. The class with the thread has a
reference to it, so that it (the observer) can be added to the state class:

form(s) ------ Observer ------ State

State notifies the observer, and the observer fires an event that can be
subscribed by multiple forms or other objects.

public delegate void MessageReceivedEventHandler(object sender,
MessageReceivedEventArgs e);
public class MessageReceivedEventArgs
{
public MessageReceivedEventArgs(string message)
{
this.Message = message;
}

public string Message; // probably make this a property, leaving it out
for brevity
}

public class SocketReadObserver
{
public event MessageReceivedEventHandler MessageReceived;
public void ActOn(string message)
{
if(this.MessageReceived != null)
this.MessageReceived(this, new
MessageReceivedEventArgs(message);
}
}

Now, your State class calls the SocketReadObserver's ActOnMethod.
You need to somehow have SocketReadObserver in scope. You could use a
singleton for this, or your main form could instance one and pass to the
class that contains the thread. Or, perhaps the object that owns the thread
also owns the observer.

The benefit of breaking it out into an observer is that any object
(including the main form) that is interested in receive events can subscribe
to the event:

public class TheThreadClass
{
public SocketReadObserver Observer;
public void TheInfiniteLoopMethod
{
//same code as before
// except provide the listener to the state class
handler.Observer = this.Observer;
}
}

// state object calls Observer.ActOn(message) when the message completes

// oh, don't forget the main form code:

// main form:
// here's the code that starts the thread:
// note the observer has to be created somewhere
// in this example, the form will do it, but you might want to originate it
elsewhere
...
SocketReadObserver observer = new SocketReadObserver();
theThreadListenerObject.Observer = observer;
observer.MessageReceived += new
MessageReceivedEventHandler(this.MessageReceived);
// note, you can hook as many interested parties as you have to this
event -- system tray item, toolbar item, whatever:

public void MessageReceived(object sender, MessageReceivedEventArgs e)
{
// important: this event will not be called by the main thread, but if
you want to do _anything_ that changes
// a gui control, you _must_ make it happen on the main thread. Use
Invoke for this:
if(this.InvokeRequired)
this.Invoke(new
MessageReceivedEventHandler(this.MessageReceived), object[] {sender, e});
else
{
// do your form stuff here
}

}


PS depending upon what you were doing, there are probably a number of ways
that you can optimize this, I was just trying to get the idea across.

PSS: If this isn't the problem that you've had, I'm sorry for being a
windbag <g>


Jim P. said:
I'm having trouble returning an object from an AsyncCallback called inside a
threaded infinite loop.

I'm working on a Peer2Peer app that uses an AsyncCallback to rerieve the
data from the remote peer. I have no problem connecting the peers and
streaming Network Streams. When the incoming data is finished recieving, I
act upon it. This works great as long as all of the code is inside my form.

I want to build the networking code into a seperate assembly (to be used by
a server and client apps). It works fine but I can't return the value back
to the Main Form. How can I pass back the retrieved data through the
following steps?? Or, any other strategies on how to tackle this problem?

The Current Process
I start the listener inside a thread and begin an infinite loop to check for
a client connection. When a client connects I call a method that starts the
.BeginRead on the Network Stream. The .BeginRead uses an AsyncCallback
delegate called 'OnReadComplete' and passes a StateObject with the connected
socket.. Inside OnReadComplete, I transfer the callback object to a new
StateObject. I recieve data in 256 byte chunks until the bytes recieved
equals 0. Then I act on that data.

Some example code:

** State Object
----------------
public class StateObject
{
public System.Net.Sockets.Socket socket;
public byte[] buffer = new byte[256];
public NetworkStream networkStream;

public AsyncCallback callbackRead;
}

** Infinite loop used after starting listener
------------------------------------------
// keep listening until you recieve a connection
for (;;)
{
// if client connects, accept the connection
// and return a new socket named socketForClient
// while tcpListener keeps listening
Socket socketForClient = tcpListener.AcceptSocket();
if (socketForClient.Connected)
{
StateObject handler = new StateObject();
handler.socket = socketForClient;
handler.networkStream = new NetworkStream(socketForClient);
handler.callbackRead = new AsyncCallback(this.OnReadComplete);
StartRead(handler);
}
}

** StartRead
-------------
public void StartRead(StateObject handler)
{
handler.networkStream.BeginRead(
handler.buffer, 0, handler.buffer.Length, handler.callbackRead, handler);
}

** OnReadComplete
----------------------
private void OnReadComplete( IAsyncResult ar )
{
// Get the socket
StateObject newSocket = (StateObject)ar.AsyncState;
Socket client = newSocket.socket;

// Get the networkStream
NetworkStream readStream = new NetworkStream(newSocket.socket);

int bytesRead = readStream.EndRead(ar);

// If there is data left to be retieved
if( bytesRead > 0 )
{
string s = System.Text.Encoding.ASCII.GetString( newSocket.buffer, 0,
bytesRead );
readStream.BeginRead( newSocket.buffer, 0, newSocket.buffer.Length,
newSocket.callbackRead, newSocket);
// Store message into string.
message += s;
}
// The incoming stream has finished
else
{
// Act on Incoming Message
actOn(message);

// Close out all objects
newSocket.networkStream.Close();
newSocket.socket.Close();
newSocket.networkStream = null;
newSocket.socket = null;
message = null;
}
}


Thanks,
Jim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top