Due to the use of Dispatcher,t he window still locks - wpf

I have a program similar to chatbot in Wpf.
I have a stack where I create the user controls I have and enter them.
I have to use Net3.5 .
The response from the server is delayed.
The problem I have is when I type and send the textbox the server does not answer,
I can not type another question and the window is locked.
Did I use Dispatcher correctly?
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
DispatchFit();
}
private void DispatchFit()
{
Dispatcher.BeginInvoke(new Action(ResponsServer), DispatcherPriority.Background);
}
public void ResponsServer()
{
Thread.Sleep(3000);
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}

When that ResponsServer() callback is being processed on your UI thread, then that Sleep is elongating the amount of time that callback is taking to process (Sleep does not pump the UI's dispatcher message queue).
If you want your callback to be done after 3 seconds, then you need to use a timer, or you can use "async" to cause a delayed processing of your callback.
Look at this question: Delayed Dispatch Invoke?
Or use this to have a BackgroundWorker do the delay and then call your ResponsServer on the UI thread (not the best code as it creates a new BackgroundWorker each time).
https://www.codeproject.com/Tips/240274/Execute-later-for-delayed-action

You are a little confused about the methods.
If I understand correctly, then Sleep is an emulation of the delay in the execution of sending a message to the server.
Then you need something like:
private async void send_ClickAsync(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
await ResponsServerAsync();
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
public async Task ResponsServerAsync()
{
Thread.Sleep(3000);
}
For .Net Framework 3.5
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
Thread thread = new Thread(ResponsServer);
thread.Start();
}
public void ResponsServer()
{
Thread.Sleep(3000);
if (Dispatcher.CheckAccess())
{
StackChildrenAdd();
}
else
{
Dispatcher.InvokeAsync(StackChildrenAdd);
}
}
private void StackChildrenAdd()
{
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}

Related

Best way to continuously monitor a MSMQ queue in WPF

I need my WPF exe to start monitoring a queue as soon as it starts up, and then respond to messages as they come in.
The way I have it now is:
public partial class App
{
readonly BackgroundWorker _worker = new BackgroundWorker();
protected override void OnStartup(StartupEventArgs e)
{
_worker.DoWork += worker_DoWork;
_worker.RunWorkerCompleted += worker_RunWorkerCompleted;
_worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_worker.DoWork += worker_DoWork;
_worker.RunWorkerAsync();
}
static void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do work
}
Obviously, this is pretty pathetic IMO.
Also, I don't like the approach of using while(true), inside the thread to keep it running indefinitely..
It's enough to read the messages from queue not permanently but periodically.
The common approach for this is using timer. For example, System.Threading.Timer.
public partial class App : Application
{
private System.Threading.Timer _msmqReadTimer;
public App()
{
_msmqReadTimer = new System.Threading.Timer(MsmqReadTimerTick);
_msmqReadTimer.Change(0, 1000); // call MsmqReadTimerTick immediatelly and each 1000 ms
}
private void MsmqReadTimerTick(object state)
{
// do work
// if you want to update some UI components after work, you should post this to UI thread dispatcher:
this.Dispatcher.Invoke(()=>
{
// logic for updating UI should be here
},
System.Windows.Threading.DispatcherPriority.Background);
}
}

Cross thread operation not valid in BackgroundWorker

I want to display some data on form load in a data gridview , the data which i want to display is in large number of rows , when i use background worker processor it show me the following error.
My code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.DataSource = obj.ListResponseFiles();
dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Form load:
private void FormFTP_Load(object sender, EventArgs e)
{
try
{
//this.comboBoxRequests.SelectedIndex = 0;
backgroundWorker1.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
There are many different ways to prevent the form from being freezed.
For example you can load your data like this:
private async void Form_Load(object sender, EventArgs e)
{
//do some initializations
await LoadData();
//do some other initializations that you need to perform.
}
private async Task LoadData()
{
//Load your data here
//For example
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
}
This way when running the form, the commands run in the sequence you wrote while the UI is responsive and you will not face with common difficulties of using BackgroundWorker or threads like Cross thread operation exceptions.
The key point is in using async/await. For more information read Asynchronous Programming with Async and Await
Remember that this way, every where you want to call LoadData, you should call it this way:
await LoadData();
And the method that you write this code in, should be async:
private async void RefreshButton_Click(object sender, EventArgs e)
{
await LoadData();
}
EDIT For .Net 4.0
For .Net 4.0 you can use both Task.Run or BackgroundWorker. I recommend Task.Run because it is more simple and more readable.
Please note Cross Thread Operation Exception will throw when you access the UI elements from another thread than UI. In this situations you should use this.Invoke(new Action(()=>{/*Access UI Here*/})); instead. And never put a time-consuming task in your invoke part.
BackgroundWorker Approach:
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
//If you put some code here for example MessageBox.Show("");
//The code will immadiately run and don't wait for worker to complete the task.
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
this.LoadData();
}
public void LoadData()
{
var ftp = new FtpUtility();
var data = ftp.ListRequestFiles();
this.Invoke(new Action(() =>
{
//Setup columns and other UI stuff
//Set datasource of grid
this.dataGridView1.DataSource = data;
}));
}
Remember everywhere you formerly used LoadData, now you should use backgroundWorker1.RunWorkerAsync(); instead.
If you want to do a job after the worker completes the task, put your job in backgroundWorker1_RunWorkerCompleted or in Invoke part of LoadData.
Task.Run Approach
private void Form1_Load(object sender, EventArgs e)
{
Task.Run(() =>
{
LoadData();
})
.ContinueWith(x =>
{
//You can put codes you want run after LoadData completed here
//If you access the UI here, you should use Invoke
});
//If you put some code here for example MessageBox.Show("");
//The code will immadiately run and don't wait for worker to complete the task.
}
public void LoadData()
{
var ftp = new FtpUtility();
var data = ftp.ListRequestFiles();
this.Invoke(new Action(() =>
{
//Setup columns and other UI stuff
//Set datasource of grid
this.dataGridView1.DataSource = data;
}));
}
Remember everywhere you formerly used LoadData, now you should use Task.Run(()=>{LoadData();}); instead.
If you want to do a job after the LoadData completes, put your job in ContinueWith or in Invoke part of LoadData.
Try this.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
dataGridViewRequest.Invoke(new Action(() => {
FTPUtility obj = new FTPUtility();
dataGridViewRequest.DataSource = obj.ListRequestFiles();
dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.DataSource = obj.ListResponseFiles();
dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void FormFTP_Load(object sender, EventArgs e)
{
try
{
//this.comboBoxRequests.SelectedIndex = 0;
backgroundWorker1.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Timer not getting called when backgroundworker running

I have a WPF window with a button that spawns a BackgroundWorker thread to create and send an email. While this BackgroundWorker is running, I want to display a user control that displays some message followed by an animated "...". That animation is run by a timer inside the user control.
Even though my mail sending code is on a BackgroundWorker, the timer in the user control never gets called (well, it does but only when the Backgroundworker is finished, which kinda defeats the purpose...).
Relevant code in the WPF window:
private void button_Send_Click(object sender, RoutedEventArgs e)
{
busyLabel.Show(); // this should start the animation timer inside the user control
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
string body = textBox_Details.Text;
body += "User-added addtional information:" + textBox_AdditionalInfo.Text;
var smtp = new SmtpClient
{
...
};
using (var message = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body
})
{
smtp.Send(message);
}
}));
}
Relevant code in the user control ("BusyLabel"):
public void Show()
{
tb_Message.Text = Message;
mTimer = new System.Timers.Timer();
mTimer.Interval = Interval;
mTimer.Elapsed += new ElapsedEventHandler(mTimer_Elapsed);
mTimer.Start();
}
void mTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
int numPeriods = tb_Message.Text.Count(f => f == '.');
if (numPeriods >= NumPeriods)
{
tb_Message.Text = Message;
}
else
{
tb_Message.Text += '.';
}
}));
}
public void Hide()
{
mTimer.Stop();
}
Any ideas why it's locking up?
Using Dispatcher.Invoke in your worker_DoWork method is putting execution back on the UI thread, so you are not really doing the work asynchronously.
You should be able to just remove that, based on the code you are showing.
If there are result values that you need to show after the work is complete, put it in the DoWorkEventArgs and you will be able to access it (on the UI thread) in the worker_RunWorkerCompleted handler's event args.
A primary reason for using BackgroundWorker is that the marshalling is handled under the covers, so you shouldn't have to use Dispatcher.Invoke.

My app has multiple windows, I want to do something when the mouse is outside of all of them for a specified time

My application has a couple of windows. I want to perform a certain action once the mouse is outside of all my windows for a specified time (say half a second).
For a single window I'd start a timer in the MouseLeave event, and delete that time in the MouseEnter event, but how would I go about implementing this for multiple windows ?
All Windows and Pages have access to App. Just start and cancel a BackGroundWorker that has a built in delay. If the worker completes then do you thing. I tested this with two pages.
Register a MouseEnter and MouseLeave on all pages
private void MainWindowsMouseLeave(object sender, MouseEventArgs e)
{
// MessageBox.Show("MouseLeave");
tbMouseEnterLeave.Text = "MouseLeave";
if (App.BackgroundWorkerApp.IsBusy) App.BackgroundWorkerApp.CancelAsync();
else
{
Thread.Sleep(10);
if (App.BackgroundWorkerApp.IsBusy)App.BackgroundWorkerApp.CancelAsync();
}
if (!App.BackgroundWorkerApp.IsBusy) App.BackgroundWorkerApp.RunWorkerAsync();
}
private void MainWindowsMouseEnter(object sender, MouseEventArgs e)
{
tbMouseEnterLeave.Text = "MouseEnter";
App.BackgroundWorkerApp.CancelAsync();
}
public partial class App : Application
{
private static System.ComponentModel.BackgroundWorker backgroundWorkerApp = new BackgroundWorker();
public App()
{
backgroundWorkerApp.WorkerSupportsCancellation = true;
backgroundWorkerApp.DoWork +=
new DoWorkEventHandler(backgroundWorkerApp_DoWork);
backgroundWorkerApp.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorkerApp_RunWorkerCompleted);
}
public static System.ComponentModel.BackgroundWorker BackgroundWorkerApp { get { return backgroundWorkerApp; } }
private void backgroundWorkerApp_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = ComputeApp(worker, e);
}
// This event handler deals with the results of the
// background operation.
private void backgroundWorkerApp_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
// MessageBox.Show("Cancel");
}
else
{
// Finally, handle the case where the operation
// succeeded.
// this where you do that thing you want to do
MessageBox.Show("Complete");
}
}
string ComputeApp(BackgroundWorker worker, DoWorkEventArgs e)
{
// Abort the operation if the user has canceled.
// Note that a call to CancelAsync may have set
// CancellationPending to true just after the
// last invocation of this method exits, so this
// code will not have the opportunity to set the
// DoWorkEventArgs.Cancel flag to true. This means
// that RunWorkerCompletedEventArgs.Cancelled will
// not be set to true in your RunWorkerCompleted
// event handler. This is a race condition.
if (worker.CancellationPending)
{
e.Cancel = true;
return "cancelled";
}
for (int i=0; i < 10; i++)
{
Thread.Sleep(100);
if (worker.CancellationPending)
{
e.Cancel = true;
return "cancelled";
}
}
return "complete";
}
}

WPF & Multi-threading questions

I'm working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking. It's my first time building such a UI, so I'm reading up on related resources on the web. My test code is thus:
private BackgroundWorker worker;
private Stopwatch swatch = new Stopwatch();
private delegate void simpleDelegate();
System.Timers.Timer timer = new System.Timers.Timer(1000);
string lblHelpPrevText = "";
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
worker = new BackgroundWorker(); //Create new background worker thread
worker.DoWork += new DoWorkEventHandler(BG_test1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end);
worker.RunWorkerAsync();
simpleDelegate del = new simpleDelegate(clockTicker);
AsyncCallback callBack = new AsyncCallback(clockEnd);
IAsyncResult ar = del.BeginInvoke(callBack, null);
lblHelpText.Text = "Processing...";
}
finally
{
worker.Dispose(); //clear resources
}
}
private void clockTicker()
{
//Grab Text
simpleDelegate delLblHelpText = delegate()
{ lblHelpPrevText = this.lblHelpText.Text; };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText);
//Start clock
timer.Elapsed += new ElapsedEventHandler(clockTick);
timer.Enabled = true;
swatch.Start();
}
private void clockTick(object sender, ElapsedEventArgs e)
{
simpleDelegate delUpdateHelpTxt = delegate()
{ this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt);
}
private void BG_test1(object sender, DoWorkEventArgs e)
{
//this.lblHelpText.Text = "Processing for 10 seconds...";
Thread.Sleep(15000);
}
private void BG_test1end(object sender, RunWorkerCompletedEventArgs e)
{
this.lblHelpText.Text = "Process done.";
this.timer.Enabled = false;
this.swatch.Stop();
this.swatch.Reset();
}
static void clockEnd(IAsyncResult ar)
{
simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate;
X.EndInvoke(ar);
}
The idea is when the button is clicked, we take the status text from a Label (e.g. "Processing...") then append the time onto it every second. I could not access the UI elements from the Timer class as it's on a different thread, so I had to use delegates to get and set the text.
It works, but is there a better way to handle this? The code seems much for such a basic operation. I'm also not fully understanding the EndInvoke bit at the bottom. I obtained the snippet of code from this thread Should One Always Call EndInvoke a Delegate inside AsyncCallback?
I understand the idea of EndInvoke is to receive the result of BeginInvoke. But is this the correct way to use it in this situation? I'm simply worried about any resource leaks but when debugging the callback appears to execute before my timer starts working.
Don't use a separate timer to read the progress of your BackgroundWorker and update the UI. Instead, make the BackgroundWorker itself "publish" its progress to the UI directly or indirectly.
This can be done pretty much anyway you want to, but there's a built-in provision exactly for this case: the BackgroundWorker.ProgressChanged event.
private void BG_test1(object sender, DoWorkEventArgs e)
{
for(var i = 0; i < 15; ++i) {
Thread.Sleep(1000);
// you will need to get a ref to `worker`
// simplest would be to make it a field in your class
worker.ReportProgress(100 / 15 * (i + 1));
}
}
This way you can simply attach your own handler to ProgressChanged and update the UI using BeginInvoke from there. The timer and everything related to it can (and should) go.
You can use timer to update UI. It is normal practice. Just instead of System.Timer.Timer I suggest use System.Windows.Threading.DispatcherTimer. The DispatcherTimer runs on the same thread as the Dispatcher. Also, instead of BackgroundWorker you can use ThreadPool.
Here is my sample:
object syncObj = new object();
Stopwatch swatch = new Stopwatch();
DispatcherTimer updateTimer; // Assume timer was initialized in constructor.
void btnStart_Click(object sender, RoutedEventArgs e) {
lock (syncObj) {
ThreadPool.QueueUserWorkItem(MyAsyncRoutine);
swatch.Start();
updateTimer.Start();
}
}
void updateTimer_Tick(object sender, EventArgs e) {
// We can access UI elements from this place.
lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds);
}
void MyAsyncRoutine(object state) {
Thread.Sleep(5000);
lock (syncObj)
Dispatcher.Invoke(new Action(() => {
swatch.Stop();
updateTimer.Stop();
lblHelpText.Text = "Process done.";
}), null);
}
private void button1_Click(object sender, EventArgs e)
{
string strFullFilePath = #"D:\Print.pdf";
ProcessStartInfo ps = new ProcessStartInfo();
ps.UseShellExecute = true;
ps.Verb = "print";
ps.CreateNoWindow = true;
ps.WindowStyle = ProcessWindowStyle.Hidden;
ps.FileName = strFullFilePath;
Process.Start(ps);
Process proc = Process.Start(ps);
KillthisProcess("AcroRd32");
}
public void KillthisProcess(string name)
{
foreach (Process prntProcess in Process.GetProcesses())
{
if (prntProcess.ProcessName.StartsWith(name))
{
prntProcess.WaitForExit(10000);
prntProcess.Kill();
}
}
}

Resources