I am trying to create a multi-client / server chat small application using WPF but I have some problems. Unfortunately when I press the Connect button the program crashes.
Well, I done that so far with the client program(with the thread):
public delegate void UpdateText(object txt);
I got that method:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox),txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
And I am using a Button_Click event to connect to the server like that:
private void connect_Click(object sender, RoutedEventArgs e)
{
if ((nick_name.Text == "") || (ip_addr.Text == "") || (port.Text == ""))
{
MessageBox.Show("Nick name, IP Address and Port fields cannot be null.");
}
else
{
client = new Client();
client.ClientName = nick_name.Text;
client.ServerIp = ip_addr.Text;
client.ServerPort = port.Text;
Thread changer = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(UpdateTextBox));
changer.Start();
client.OnClientConnected += new OnClientConnectedDelegate(client_OnClientConnected);
client.OnClientConnecting += new OnClientConnectingDelegate(client_OnClientConnecting);
client.OnClientDisconnected += new OnClientDisconnectedDelegate(client_OnClientDisconnected);
client.OnClientError += new OnClientErrorDelegate(client_OnClientError);
client.OnClientFileSending += new OnClientFileSendingDelegate(client_OnClientFileSending);
client.OnDataReceived += new OnClientReceivedDelegate(client_OnDataReceived);
client.Connect();
}
}
Please note that the OnClient* events are like private void client_OnDataReceived(object Sender, ClientReceivedArguments R) { UpdateTextBox(R.ReceivedData); }
So these events should write some text like "Connected" into the msg_log TextBox
PS. The txt object used to be a string variable but I change it since ParameterizedThreadStart only accepts objects as parameters as I know.
Any help would be greatly appreciated!
Thanks in advance,
George
EDIT: Edited the UpdateTextBox method as Abe Heidebrecht suggested.
There are a few things wrong with your Invoke calls.
You don't need to create an object array to pass the parameters.
Passing DispatcherPriority.Normal is redundant (Normal is default).
You aren't passing any parameters to the second Invoke method (which is probably where your error is happening).
It should look like this:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
EDIT For StackOverflowException
This will cause a StackOverflowException because you are calling your method in an infinite loop. This is happening because your method is simply calling itself over and over again.
What Dispatcher.Invoke does is invoke the passed delegate on the thread that owns the Dispatcher. Just because the msg_log may have a different dispatcher, when you were calling UpdateTextBox, you were passing a delegate to the current method, which causes the infinite loop.
What you really need to do is call a method on the msg_log object, like so:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
if (txt != null)
msg_log.Text = txt.ToString();
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
Related
Sorry for the title, i didn't find it easy to resume.
My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.
So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.
In the dll i've come up with:
public class Reader
{
public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
public void Scan(String ReaderName)
{
AlertEventArgs alertEventArgs = new AlertEventArgs();
alertEventArgs.uuiData = null;
//Code with blocking scan function here
if (ScanFinnished)
{
alertEventArgs.uuiData = "Scan Finnished!";
}
alertEventArgs.cardStateData = readerState[0].eventState;
ReaderEvent(new object(), alertEventArgs);
}
public event ReaderEventHandler ReaderEvent;
}
public class AlertEventArgs : EventArgs
{
#region AlertEventArgs Properties
private string _uui = null;
private uint cardState = 0;
#endregion
#region Get/Set Properties
public string uuiData
{
get { return _uui; }
set { _uui = value; }
}
public uint cardStateData
{
get { return cardState; }
set { cardState = value; }
}
#endregion
}
While in the main app I do:
Reader reader;
Task polling;
String SelectedReader = "Some_Reader";
private void bButton_Click(object sender, EventArgs e)
{
reader = new Reader();
reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}
void reader_EventChanged(object sender, AlertEventArgs e)
{
MessageBox.Show(e.uuiData + " Estado: " + e.cardStateData.ToString("X"));
reader.Dispose();
}
So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.
Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.
What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?
When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.
You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.
For example:
private void RaiseReaderEvent(AlertEventArgs args)
{
var myEvent = ReaderEvent; // This prevents race conditions
if (myEvent != null) // remember to check that someone actually subscribes your event
myEvent(this, args); // Sender should be *this*, not some "new object()".
}
Though I personally like a bit more generic approach:
private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
var eventInstance = oEvent;
if (eventInstance != null)
eventInstance(this, args);
}
Which can then be used to raise all events in same class like this:
Raise(ReaderEvent, alertEventArgs);
Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.
In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.
So your UI-thread safe event handler should be something like this:
void reader_EventChanged(object sender, AlertEventArgs e)
{
if (InvokeRequired) // This true for others than UI Thread.
{
Invoke((MethodInvoker)delegate
{
Text = "My new title!";
});
}
else
Text = "My new title!";
}
In WPF there's Dispather that handles similar invoking.
After making the project simpler, I believe I identified the problem is actually a result the async marshalling.
UPDATE: I made the code simpler to try to figure out what was going on. So here is an update... The Observable collection is being populated on a new thread (async method). I tried moving the assigning of the ItemsSource to after the ObservableCollection is loaded as seen below
async void LoadAllData(object sender, EventArgs e)
{
if (sender != null)
{
App.GeoLocationComplete -= LoadAllData;
}
await ViewModelObjects.NearbyLocations.LoadLocationData();
lvPlaces.ItemsSource = ViewModelObjects.NearbyLocations.GBSLocationDetails;
}
The definition for the data load method is a follows:
public async Task LoadLocationData()
{....}
When I run this code I get the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I know what is causing the error (the data was loaded on a thread other than the UI thread) but I don't know how to fix it. Suggestions?
UPDATE UPDATE: So I believe I have identified the root cause of the problem but have not figured out how to fix it. I started by simplifying my code as follows and it worked.
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
LoadAllData(null, null);
}
void LoadAllData(object sender, EventArgs e)
{
lobj_Places = new ObservableCollection<GBSLocationDetail>()
{
new GBSLocationDetail()
{
Title = "Location 1",
Distance = "20 Miles",
AddInfo = "Something Else",
AttributesTexts="Gay, Bar, Dance"
}
};
lvPlaces.ItemsSource = lobj_Places;
}
HOWEVER, what I need is for the LoadAllData method to be called once I have the GPS location from the device. So in my App.XAML.cs I have the following delegate event declared:
public static Plugin.Geolocator.Abstractions.IGeolocator gobj_RealGeoCoordinator;
public static event GeoLocationCompleteEventHandler GeoLocationComplete;
public static bool gb_WaitingForLocation = true;
Then I have the following code call the event once I get the location back from the device:
private async void ProcessStartupandResume()
{
if (gobj_RealGeoCoordinator == null)
{
gobj_RealGeoCoordinator = CrossGeolocator.Current;
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
if (!ViewModelObjects.AppSettings.CanAccessLocation)
{
await MainPage.DisplayAlert(ResourceStrings.GetValue("NoLocationServicesTitle"), ResourceStrings.GetValue("NoLocationServicesMessage"), ResourceStrings.GetValue("OKButtonText"));
}
//Only add the events if the object has to be created.
gobj_RealGeoCoordinator.PositionChanged += gobj_RealGeoCoordinator_PositionChanged;
gobj_RealGeoCoordinator.PositionError += (sender, e) =>
{
ProcessException(new Exception(e.Error.ToString()));
};
}
//Set this to null to trigger the first check
ib_GPSReenabled = null;
if (gobj_RealGeoCoordinator.IsListening)
await gobj_RealGeoCoordinator.StopListeningAsync();
gobj_RealGeoCoordinator.DesiredAccuracy = 50;
await gobj_RealGeoCoordinator.StartListeningAsync(10000, 20);
}
private static void gobj_RealGeoCoordinator_PositionChanged(object sender, PositionEventArgs e)
{
var pos = e.Position;
ViewModelObjects.AppSettings.Latitude = pos.Latitude;
ViewModelObjects.AppSettings.Longitude = pos.Longitude;
if (gb_WaitingForLocation)
{
gb_WaitingForLocation = false;
GeoLocationComplete?.Invoke(new object() , null);
}
}
Then in my page I subscribe to the GeoLocationComplete event using the LoadAllData method as seen below. Even when I use a local object and try to set the ItemsSource for the ListView in the code when executed as a result of the event being raised, I receive the error. See code below which subscribed to the event:
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
if (App.gb_WaitingForLocation)
App.GeoLocationComplete += LoadAllData;
else
LoadAllData(null, null);
}
Any suggestions on how to fix this?
OK so I figured it out. I needed to invoke the event on the main thread and I did that with the following code:
Device.BeginInvokeOnMainThread(() =>
{
GeoLocationComplete?.Invoke(new object(), null);
});
After inserting this code, the error was gone. Changing the code back to simply
GeoLocationComplete?.Invoke(new object(), null);
cause the error to occur again. Thus I believe this resolved my problem. Hope this helps someone else. :)
I am completely lost in what is really causing the problem. So rather trying to explain the problem, I might as well as get straight to the code with the problem. Here is the layout of my program:
private void connection_OnMessage(object sender, agsXMPP.protocol.client.Message msg)
{
if (!string.IsNullOrEmpty(msg.Body) && ((msg.XDelay != null && msg.XDelay.Stamp.Date == DateTime.Today) || msg.XDelay == null))
{
agsXMPP.Jid JID = new Jid(msg.From.Bare);
int rowIndex = chatLog.Rows.Add();
chatLog.Rows[rowIndex].Cells["chatNameColumn"].Value = JID.User;
chatLog.Rows[rowIndex].Cells["chatMessageColumn"].Value = msg.Body;
//Begin line of the problem
if (IncomingMessage != null)
IncomingMessage(this, JID.User, msg.Body);
//End of the problem
}
}
The above code snippet is of class A. After starting up the program, this class makes the connection to the server. Right after being connected, this code snippet is rapidly fired about 20 times, once per line of message. (There are already about 20 lines of message in the chat log.) Since only one message makes it through the if condition, the lines commented with the problem is only run once. Those lines fire the code snippet below of class B.
(Around the time class A is firing, I have another class like A that fires the similar event to be handled by class B the same way, which will be handled by class C.)
private void newSource_IncomingMessage(IChatSource sender, string user, string message)
{
UpdatedMessageEventHandler temp = UpdatedMessage;
if (temp != null)
temp(sender, user, message);
}
The above code snippet of class B fires the code snippet below of class C.
private void chatManager_UpdatedMessage(IChatSource source, string user, string message)
{
if (!source.Muted)
{
updateMessage(source, user, message);
}
}
delegate void UpdateMessageCallback(IChatSource source, string user, string message);
private void updateMessage(IChatSource source, string user, string message)
{
if (allDataGridView.InvokeRequired)
{
UpdateMessageCallback d = new UpdateMessageCallback(updateMessage);
Invoke(d, new object[] { source, user, message });
}
else
{
int row = allDataGridView.Rows.Add();
allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;
if (!MenuItem.Checked)
{
MenuItem.Checked = true;
Show();
}
}
}
Here is what I tried to do to fix the problem, but the code is removed already:
I tried adding lock to certain codes.
I tried to put the certain codes on a separate thread and have them run.
Here is what happened:
When I run the program, the UI thread seems to be blocked. In other words, class C doesn't get painted. Sometimes, the form doesn't even appear.
A few times, it complaint about a strange error "An error occurred invoking the method. The destination thread no longer exists."
If I commented out the problem lines, everything work fine, but here is the strange part. If I create a timer object in class A and have it fired the event the same way, it works fine.
While line stepping in debug mode, I sometimes got it work fine, but majority of the time, it fails.
For a few times, I run into InvalidOperationException with the message, "Control accessed from a thread other than the thread it was created on." even though I did make it thread safe.
In conclusion, I don't know what is causing the UI thread to be blocked. Any pointer or what I might do wrong?
The problem is that you're calling methods crossthreading. This can lead to deadlocks.
You could solve this, adding the messages on a concurrent queue and on a timer (gui thread) checking the queue and adding the messages to controls.
This is not a complete solution, but a method to prevent crossthread method invoking
Like: (PSEUDO) (wrote online on the site)
// dataholder
public class ChatMsg
{
public string User {get;set;}
public string Message {get;set;}
}
// message store
private List<ChatMsg> _messages = new List<ChatMsg>();
// timer
private Timer _timer;
// callback for you chatapi?? (like you wrote)
private void newSource_IncomingMessage(IChatSource sender, string user, string message)
{
UpdatedMessageEventHandler temp = UpdatedMessage;
// lock the store
lock(_messages)
_messages.Add(new ChatMsg { User = user, Message = message });
}
// constructor
public Form1()
{
// create the check timer.
_timer = new Timer();
_timer.Interval = 100;
_timer.Tick += Timer_Tick;
_timer.Start();
}
// timer method
private void Timer_Tick(object sender, EventArgs e)
{
// copy of the queue
ChatMsg[] msgs;
// lock the store and 'move' the messages
lock(_messages)
{
msgs = _messages.ToArray();
_messages.Clear();
}
if(msgs.Length == 0)
return;
// add them to the controls
foreach(var msg in msgs)
{
// add the message to the gui controls... (copied from your question)
int row = allDataGridView.Rows.Add();
allDataGridView.Rows[row].DefaultCellStyle.ForeColor = source.TextColor;
allDataGridView.Rows[row].Cells["NameColumn"].Value = user;
allDataGridView.Rows[row].Cells["MessageColumn"].Value = message;
}
}
Something like that..
In my apps i use backgroundWorker, to set text in some TextBox, I need first to invoke that TextBox.
First I use:
if (someTextBox.InvokeRequired)
{
someTextBox.Invoke((MethodInvoker)delegate
{
someTextBox.Text = "some_text";
});
}
else
{
someTextBox.Text = "some_text";
}
This method work for me fine, but because i have multiple TextBox-es i wrote:
private void invComp(TextBox txtBox, String str)
{
if (txtBox.InvokeRequired)
{
txtBox.Invoke((MethodInvoker)delegate
{
txtBox.Text = str;
});
}
else
{
txtBox.Text = str;
}
}
It is better to invoke it on this way? (invComp(someTextBox, "some_text");
Or maybe i have some third, bether , way?
I invoke some buttons to, I was think to write something like this for button to, if this is ok?
Tnx
Control.InvokeRequired suffers from cargo cult. You are updating a control from a worker thread, you know that invoking is required. So there is absolutely no point in testing it. Except for one reason, there is something fundamentally wrong when it is false. Which happens a lot more often than programmers like, forgetting to stop a worker when the user closes the window is a traditional bug. This causes all kind of mayhem, you want to know about it:
private void invComp(TextBox txtBox, String str) {
if (!this.InvokeRequired) throw new InvalidOperationException("You forgot to stop the worker");
this.BeginInvoke(new Action(() => txtBox.Text = str));
}
Short and snappy and fail-safe and fast. Good qualities of code. Note that it uses the form's BeginInvoke() method, it doesn't depend on a child control being created. And that it uses BeginInvoke() instead of Invoke(), important to not bog down the worker thread and avoid deadlock. Always avoid Invoke(), it is only required when you need to know a method return value.
A completely different take is to focus on you using BackgroundWorker. It already marshals calls to the UI thread, it is just that the method has a clumsy name. You can get the ProgressChanged event to execute any code, it isn't just good enough to show progress. Write your event handler like this:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
((Action)e.UserState).Invoke();
}
Now you can make it execute any code on the UI thread:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
//...
worker.ReportProgress(0, new Action(() => textBox1.Text = "hello"));
}
You can slightly modify your method, to make it generic, so that you can use if for any control.
private void invComp<T>(T control, String str) where T: Control
{
if (control.InvokeRequired)
{
control.Invoke((MethodInvoker)delegate
{
control.Text = str;
});
}
else
{
control.Text = str;
}
}
I created a thread.
How can I read values of variables from GUI thread?
I know how to change them. I don't know how to read them.
This is what I'm using for changing things on GUI thread:
public void Log(string message)
{
MethodInvoker m = () => { Log_textBox.Text += message; };
if (InvokeRequired)
{
BeginInvoke(m);
}
else
{
Invoke(m);
}
}
I need to read some values from GUI thread:
public void StartBot()
{
Klient.StartBot(selectedType, (int)nb_count.Value, nb_nonstop.Checked, (...)int.Parse(extra_set.SelectedItem.ToString()));
}
private void StartStopButton_Click(object sender, EventArgs e)
{
Thread questThread = new Thread(StartBot);
Klient.RequestToStop = false;
questThread.Start();
}
I'm getting cross thread operation error on line with Klient.StartBot(...) argument list.
How to fix it?
You can return a value from an invoked delegate by creating a Func<ReturnType>.
Invoke will return the function's return value.
Alternatively, you can set a local variable inside the delegate and call it using Invoke (not BeginInvoke, since that won't wait for it to finish)