I'm having some trouble with my MVP solution, probably threading related. I'm running Compact Framework 3.5 and using C#. I can use OpenNETCF, so BackgroundWorker is available to me.
I have a piece of code (MyClient
) that connects to a web server using sockets. Code connects to the server and downloads the data (endlessly, its a stream) until the user stops it. Because the downloading of data is endless it must be run in a thread, and I think this is where I get issues.
The MyClient
object has an state, represented as an enum On
, Off
, Connecting
.
Edit - Just to clarify, when MyClient.Start() is called it connects to the server. It then takes that connection and saves it for use in the Thread run to constantly download data. So when Stop() is called it just needs to get a bool flag to tell the thread used inside MyClient to Stop. Shortened version below for clarity.
public void Start()
{
//...
//Code to Connect to server...
stream = _connection.GetStream();
//...
//Code to send/receive data to confirm connection...
State = State.On;
//Start thread to read data constantly until stopped by user setting "_continueReadingData = false"
_continueReadingData = true;
Thread readData = new Thread(ReadData);
readData.IsBackground = true;
readData.Start();
//Note readData uses the stream variable saved above
}
View calls presenter with _presenter.TurnOn();
. Presenter calls model with _model.Start();
. The idea is the MyClient code is started, reports its status changes and runs endless in the background until the user clicks stop. The View
is protected with Invoke/BeginInvoke calls on the UI components.
I've attached a code sample of my model below. Originally I used a normal thread and got it working, as you can see below it is commented out. Two issues here, the need to use Invoke to marshall back to the UI thread for everything that reaches the view, and also The issue here is any exceptions raised don't return to the UI thread, so instead cannot be handled and will crash the application. These are the two issues I am trying to address.
I've since tried the BackgroundWorker (available in OpenNETCF, just like the normal BackgroundWorker in .Net 2.0 onwards), to handle exceptions and marshalling as in code below. But with this I can't get it to work. Instead when State is changed and reported back to the GUI. Although Invoke is called, it complains with InvalidOperationException - "Invoke or BeginInvoke cannot be called on a control until the window handle has been created"
. Doing some research it almost sounds like the thread is creating its own set of controls. At this point I am confused.
Can anyone lend a hand to show me how to properly start/end the threads in the model so they run in the background, raise exceptions back to the model to be handled, and marshall execution back to the UI thread so you don't have to use Invoke on every control. I'm sure it must be possible.
public class Model
{
public event EventHandler DataChanged;
public event EventHandler ErrorRaised;
private MyClient _client = new MyClient();
public Model()
{
//Register to events
_client.StateChanged += ClientStateChanged;
//Setup current values
State = _client.State;
}
void ClientStateChanged(NTRIPClient client, NTRIPState newState)
{
State = newState;
}
private State _state;
public State State
{
get { return _state; }
set
{
if (_state != value)
{
_state = value;
if (DataChanged != null)
{
DataChanged(this, EventArgs.Empty);
}
}
}
}
public void Start()
{
//Thread thread = new Thread(_NTRIPClient.Start);
//thread.IsBackground = true;
//thread.Start();
BackgroundWorker bgWorker = new BackgroundWorker();
bgWorker.DoWork += _client.Start();
bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
if (ErrorRaised != null)
{
ErrorRaised(this, new ErrorEventArgs(e.Error));
}
}
}
}