C# backgroundWorker cancellation and invoke - c#

I have 2 questions about backgroundWorker: one is cancellation and another is invoking.
My code briefly looks like this:
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
}
for (int i = 0; i < 10; i++) {
System.Threading.Thread.Sleep(50);
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
EditProcess.Text(String.Format("Downloaded: {0}\r\n"));
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
First, while DnldBgWorker is running, I clicked StopBtn to stop the DnldBgWorker and the asynchronous work would not stop. How should I stop DnldBgWorker?
Second, EditProcess.Text(String.Format("Downloaded: {0}\r\n")); would give me an error that cross-thread operation is not valid. I know that I should make a delegate to do this, but I don't know exactly how.
++) My code looks like it's doing very simple works in very complicated way, but I put really essential elements in this code so please understand

Let's address the issue before we get into the code
For some reason, you have a completely redundant loop waiting for cancel after your actual download is done. Hence BtnStop does not work for you
When you call EditProcess.Text from Dnld which is invoked in the BackgroundWorker context, you are accessing a GUI element from a thread which does not "own" it. You can read in detail about cross-thread operation here. In your case, you should do it via your ReportProgress call.
Now you can see how I have
Removed the redundant loop from GoDownload while moving the if (DnldBgWorker.CancellationPending) check to the download loop. This should make the StopBtn work now.
Added the ProgressChanged event handler to do the GUI change in the ExecuteBtn_Click. This is triggered by DnldBgWorker.ReportProgress call in the download loop of GoDownload method. Here we pass the custom formatted string as UserState
Also make sure that you have the enabled the ReportsProgress and SupportsCancellation properties like below, perhaps in your designer property box or in code lile DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;
Hope everything else is clear with the code below.
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
DnldBgWorker.ProgressChanged += (s, e) => EditProcess.Text((string)e.UserState);;
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
DnldBgWorker.ReportProgress(0, String.Format($"Downloaded: {url}\r\n"));
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}

There are 2 issues here:
Regarding cancellation - you need to check for cancellation status in the loop that does downloading (thus downloading only part of requested files), not in the later loop which I don't really understand.
As an additional side note you can avoid using BackgroundWorker by using WebClient.DownloadFileAsync and WebClient.CancelAsync combo.
As of reporting progress - make you BackgroundWorker report progress back to the UI thread via ReportProgress and update UI from there.

As for how to cancel a thread. Here is a basic example, for a console application, that I hope you can fit into your more complex code.
void Main()
{
var tokenSource = new CancellationTokenSource();
System.Threading.Tasks.Task.Run(() => BackgroundThread(tokenSource.Token));
Thread.Sleep(5000);
tokenSource.Cancel();
}
private void BackgroundThread(CancellationToken token)
{
while (token.IsCancellationRequested == false) {
Console.Write(".");
Thread.Sleep(1000);
}
Console.WriteLine("\nCancellation Requested Thread Exiting...");
}
The results would be the following.
.....
Cancellation Requested Thread Exiting...
Secondly, as far as how to invoke from your thread to interact with the user interface, hopefully this blog will help you. Updating Windows Form UI elements from another thread
Please let me know if you found this helpful.

To support cancellation you need to set the property
DnldBgWorker.WorkerSupportsCancellation = true;
It is not clear if you set it somewhere else, but you need it to cancel the background worker as you can read on MSDN
Set the WorkerSupportsCancellation property to true if you want the
BackgroundWorker to support cancellation. When this property is true,
you can call the CancelAsync method to interrupt a background
operation.
Also I would change the GoDownload method to
private void GoDownload(Download Dnld, string[] urllist, EventArgs e)
{
foreach(string url in urllist)
{
Dnld.Dnld(url);
// this is just to give more time to test the cancellation
System.Threading.Thread.Sleep(500);
// Check the cancellation after each download
if (DnldBgWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
}
For the second problem you need to call that method when your code is running on the UI thread and not in the background thread. You could easily achieve this moving the textbox update in the event handler for the ProgressChanged event. To set up the event handler you need another property set to true
DnldBgWorker.WorkerReportsProgress = true;
And set the event handler for the ProgressChanged event
DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;
private void DnldBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
EditProcess.Text(String.Format("Downloaded: {0}\r\n", e.ProgressPercentage));
}
and raise this event in the GoDownload with
DnldBgWorker.ReportProgress(i);

Related

Accessing controls in Form from a derived BackgroundWorker class results in cross-thread error

I'm working on writing a class which is derived from the System.ComponentModel.BackgroundWorker class. The reason I am doing so in my project is that I need a lot of information to be returned in different types of status update events, depending on which event is raised. When attempting to update any of the controls the main form from any of my update events, I am getting the following error:
System.InvalidOperationException: 'Cross-thread operation not valid:
Control '' accessed from a thread other than the thread it was created
on.'
The first control that I am attempting to update is a ToolStripStatusLabel, which does not have an .Invoke() method. I have created minimally verifiable example below. To recreate the error, simply create a new Windows Forms App (.NET Framework) project targeted to .NET 4.8 and copy paste the following code into the Form1.cs file:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private StatusStrip statusStrip1;
private ToolStripStatusLabel toolStripStatusLabel1;
private ToolStripProgressBar toolStripProgressBar1;
private Button button1;
private MyBGW myBGW;
public Form1()
{
InitializeComponent();
this.statusStrip1 = new StatusStrip();
this.toolStripStatusLabel1 = new ToolStripStatusLabel() { Text = "Starting Text" };
this.toolStripProgressBar1 = new ToolStripProgressBar();
this.button1 = new Button();
this.myBGW = new MyBGW();
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {this.toolStripStatusLabel1, this.toolStripProgressBar1});
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.button1);
this.button1.Click += Button1_Click;
this.myBGW.OnMyBGW_StatusChanged += MyBGW_OnMyBGW_StatusChanged;
}
private void Button1_Click(object sender, EventArgs e) { myBGW.RunWorkerAsync(); }
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
// The following two lines will throw the cross-threading exception
this.toolStripStatusLabel1.Text = e.StatusText;
if (e.PBarStyle != MyBGW.pBarStyles.NoChange) { this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle; }
}
}
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void MyBGW_StatusChanged_EventHandler(object sender, MyBGW_StatusChanged_EventArgs e);
public event MyBGW_StatusChanged_EventHandler OnMyBGW_StatusChanged;
public class MyBGW_StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public MyBGW_StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(10000);
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
}
}
My best guess is that I am raising or consuming the event incorrectly which is causing the code to still be run on the worker thread instead of the main/UI thread, but I'm coming up short in my research on what I'm missing.
EDIT: this question is not related to Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on as it is not directly relying on a BackgroundWorker but is rather attempting to add additional events to a derived class, of which the addition of those events are causing the Cross-Thread exception. Also, the answer does not apply as the control attempting to be updated does not have the .Invoke method as the solution to that question stated.
The problem for this question is in relation to how the event was being raised, which was incorrectly, causing the consumption of that event to be on the wrong thread and raising the cross-thread exception.
The BackgroundWorker.DoWork event handler is supposed to do background work, and it's not intended for interacting with the UI. This handler is invoked on a ThreadPool thread, and interacting with UI components from any thread other than the UI thread is not allowed. The BackgroundWorker class offers two events that are raised on the UI thread¹, the ProgressChanged and the RunWorkerCompleted. You could take advantage of this, by invoking your StatusChanged event on the ProgressChanged event handler (or overriding the OnProgressChanged method), and passing your StatusChangedEventArgs as an argument of the ReportProgress method:
public class MyBGW : BackgroundWorker
{
public enum BarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void StatusChangedEventHandler(object sender,
StatusChangedEventArgs e);
public event StatusChangedEventHandler StatusChanged;
public MyBGW() { this.WorkerReportsProgress = true; }
public class StatusChangedEventArgs : EventArgs
{
public string StatusText;
public BarStyles PBarStyle;
public StatusChangedEventArgs(string statusText, BarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
protected override void OnDoWork(DoWorkEventArgs e)
{
this.ReportProgress(-1,
new StatusChangedEventArgs(DateTime.Now.ToString(), BarStyles.Marquee));
base.OnDoWork(e);
this.ReportProgress(-1,
new StatusChangedEventArgs("Done", BarStyles.Continuous));
}
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == -1 && e.UserState is StatusChangedEventArgs args)
StatusChanged?.Invoke(this, args);
else
base.OnProgressChanged(e);
}
}
¹ To be precise, the ProgressChanged and RunWorkerCompleted events are raised on the SynchronizationContext.Current which is captured when the BackgroundWorker.RunWorkerAsync is invoked.
Because toolStripStatusLabel1 And toolStripProgressBar1 runs inside a thread other than the main thread, it needs to be Invoke. And since ToolStripStatusLabel And ToolStripProgressBar itself does not have an Invoke method, we use its parent Invoke method.
change MyBGW_OnMyBGW_StatusChanged to :
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
InvokeIfRequired(this, ()=>
{
this.toolStripStatusLabel1.Text = e.StatusText;
});
if (e.PBarStyle != MyBGW.pBarStyles.NoChange)
{
InvokeIfRequired(this, () =>
{
this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle;
});
}
}
add InvokeIfRequired method
public void InvokeIfRequired(Control control, MethodInvoker action)
{
if (control.InvokeRequired)
control.Invoke(action);
else
action();
}
As mjwills has stated in the comments of the question, I was not raising the event properly, which was causing the event to be consumed on the same worker thread. After looking at the link for the .NET source code of the BackgroundWorker class, I can see that there is a bit of code, AsyncOperation.Post() that has the method protected virtual void OnStatusChangedin the code below raised in the main thread rather than the worker thread.
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
private static readonly object statusChangedKey = new object();
private AsyncOperation asyncOperation = null;
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
public delegate void StatusChanged_EventHandler(object sender, StatusChanged_EventArgs e);
public event StatusChanged_EventHandler StatusChanged
{
add { this.Events.AddHandler(statusChangedKey, value); }
remove { this.Events.RemoveHandler(statusChangedKey, value); }
}
protected virtual void OnStatusChanged(StatusChanged_EventArgs e) { ((StatusChanged_EventHandler)Events[statusChangedKey])?.Invoke(this, e); }
private void StatusReporter(object arg) { OnStatusChanged((StatusChanged_EventArgs)arg); }
public void UpdateStatus(StatusChanged_EventArgs e) { asyncOperation.Post(new System.Threading.SendOrPostCallback(StatusReporter), e); }
public class StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { asyncOperation = AsyncOperationManager.CreateOperation(null); base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
UpdateStatus(new StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(3000);
UpdateStatus(new StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
}
I don't fully understand the how and why, but it works. Hopefully someone can comment below with a better explanation.

WinForms App w/BackgroundWorker & Class Object Events

I'm working on a WinForms application that does a bunch of File Processing. This processing is done in multiple class objects that have "events" to publish different types of messages. One type is a "status" of what is being worked on and Two is an "output" that documents issues or change that were made.
I added a BackgroundWorker process and have that working fine but the only notifications I see is a ProgressPercentage. If I subscribe & raise any of the class events I get a "Cross-thread operation .. " which is understandable. What is the best way to implement this so that these components can be used by a winforms app and a non interactive process as well?
Here is what my DoWork looks like now, but the events cause the "Cross-thread":
var search = SearchToolFactory.Get(Convert.ToInt32(checkedTypeButton.Tag));
search.RaiseUpdateSearchEvent += new EventHandler<UpdateEventArgs>(search_RaiseUpdateSearchEvent);
search.RaiseUpdateOutputEvent += new EventHandler<UpdateEventArgs>(search_RaiseUpdateOutputEvent);
search.Process(Convert.ToInt32(checkedScopeButton.Tag), txtInput.Text, txtPattern.Text);
And here is the UpdateEventArgs, just a smilple class to pass a message:
public class UpdateEventArgs: EventArgs
{
public UpdateEventArgs(string s)
{
update = s;
}
private string update;
public string Update
{
get { return update; }
set { update = value; }
}
}
So based on PMF's comment I got the following to work:
void search_RaiseUpdateOutputEvent(object sender, UpdateEventArgs e)
{
if (InvokeRequired)
{
this.Invoke( (MethodInvoker)delegate{ txtOutput.Text += e.Update; });
}
}
void search_RaiseUpdateSearchEvent(object sender, UpdateEventArgs e)
{
if (InvokeRequired)
{
this.Invoke((MethodInvoker)delegate { txtSearching.Text = e.Update; });
}
}
But also see and understand the concern Hans Passant stated about structural issues and that my architecture is flawed and still needs additional work.
Thanks
dbl
BackgroundWorker.ReportProgress() has an overload with two arguments. The second can be anything you want. Like a delegate:
public event EventHandler Foo;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
//...
backgroundWorker1.ReportProgress(0, new Action(() => {
var handler = Foo;
if (handler != null) handler(this, EventArgs.Empty);
}));
//...
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
if (e.UserState != null) ((Action)e.UserState)();
else this.progressBar1.Value = e.ProgressPercentage; // optional
}
You need to Invoke() any user-interface event from your background-worker thread. Or you invoke the actual update in the form code (the later solution is probably the nicer way because it keeps the UI logic to the UI part of the application).
That would look similar to the following (for an event handler listening to status changes):
public void OnStatusUpdated(Status newStatus)
{
if (InvokeRequired)
{
Invoke(delegate
{
statusControl.Text = newStatus.ToString(); // Or something like it.
});
}
}

User interaction in non-UI thread?

in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?

How can i use a BackgroundWorker with a timer tick?

Decided to not use any timers.
What i did is simpler.
Added a backgroundworker.
Added a Shown event the Shown event fire after all the constructor have been loaded.
In the Shown event im starting the backgroundworker async.
In the backgroundworker DoWork im doing:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while(true)
{
cpuView();
gpuView();
Thread.Sleep(1000);
}
}
In this case it's better to use two System.Threading.Timer and execute your cpu-intensive operations in these two threads. Please note that you must access controls with BeginInvoke. You can encapsulate those accesses into properties setter or even better pull them out to a view model class.
public class MyForm : Form
{
private System.Threading.Timer gpuUpdateTimer;
private System.Threading.Timer cpuUpdateTimer;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!DesignMode)
{
gpuUpdateTimer = new System.Threading.Timer(UpdateGpuView, null, 0, 1000);
cpuUpdateTimer = new System.Threading.Timer(UpdateCpuView, null, 0, 100);
}
}
private string GpuText
{
set
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => gpuLabel.Text = value), null);
}
}
}
private string TemperatureLabel
{
set
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => temperatureLabel.Text = value), null);
}
}
}
private void UpdateCpuView(object state)
{
// do your stuff here
//
// do not access control directly, use BeginInvoke!
TemperatureLabel = sensor.Value.ToString() + "c" // whatever
}
private void UpdateGpuView(object state)
{
// do your stuff here
//
// do not access control directly, use BeginInvoke!
GpuText = sensor.Value.ToString() + "c"; // whatever
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (cpuTimer != null)
{
cpuTimer.Dispose();
}
if (gpuTimer != null)
{
gpuTimer.Dispose();
}
}
base.Dispose(disposing);
}
You can't just throw this code into a background worker and expect it to work. Anything that updates UI elements (labels, textboxes, ...) needs to be invoked on the main thread. You need to break out your logic to get the data and the logic to update the UI.
I would say your best bet is to do this:
In the timer Tick() method:
// Disable the timer.
// Start the background worker
In the background worker DoWork() method:
// Call your functions, taking out any code that
// updates UI elements and storing this information
// somewhere you can access it once the thread is done.
In the background worker Completed() method:
// Update the UI elements based on your results from the worker thread
// Re-enable the timer.
First make sure to get your head around multithreathing and it's problems (especially UI stuff).
Then you can use somethink like
public class Program
{
public static void Main(string[] args)
{
Timer myTimer = new Timer(TimerTick, // the callback function
new object(), // some parameter to pass
0, // the time to wait before the timer starts it's first tick
1000); // the tick intervall
}
private static void TimerTick(object state)
{
// less then .NET 4.0
Thread newThread = new Thread(CallTheBackgroundFunctions);
newThread.Start();
// .NET 4.0 or higher
Task.Factory.StartNew(CallTheBackgroundFunctions);
}
private static void CallTheBackgroundFunctions()
{
cpuView();
gpuView();
}
}
Please keep in mind (just like John Koerner told you) your cpuView() and gpuView() will not work as is.
Yes you can:
In your Timer tick event:
private void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
backgroundworker.RunWorkerAsync();
timer.Enabled = true;
}
In your Backgroundworker dowork event:
private void backgroundworker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
//Write what you want to do
}
catch (Exception ex)
{
MessageBox.Show("Error:\n\n" + ex.Message, "System", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
I think BackgroundWorker is too complex thing for the case; with Timer it is difficult to implement guaranteed stopping.
I would like to recommend you using worker Thread with the loop which waits cancellation ManualResetEvent for the interval you need:
If the cancellation event is set then the worker exits the loop.
If there is a timeout (time interval you need exceeds) then perform system monitoring.
Here is the draft version of the code. Please note I have not tested it, but it could show you the idea.
public class HardwareMonitor
{
private readonly object _locker = new object();
private readonly TimeSpan _monitoringInterval;
private readonly Thread _thread;
private readonly ManualResetEvent _stoppingEvent = new ManualResetEvent(false);
private readonly ManualResetEvent _stoppedEvent = new ManualResetEvent(false);
public HardwareMonitor(TimeSpan monitoringInterval)
{
_monitoringInterval = monitoringInterval;
_thread = new Thread(ThreadFunc)
{
IsBackground = true
};
}
public void Start()
{
lock (_locker)
{
if (!_stoppedEvent.WaitOne(0))
throw new InvalidOperationException("Already running");
_stoppingEvent.Reset();
_stoppedEvent.Reset();
_thread.Start();
}
}
public void Stop()
{
lock (_locker)
{
_stoppingEvent.Set();
}
_stoppedEvent.WaitOne();
}
private void ThreadFunc()
{
try
{
while (true)
{
// Wait for time interval or cancellation event.
if (_stoppingEvent.WaitOne(_monitoringInterval))
break;
// Monitoring...
// NOTE: update UI elements using Invoke()/BeginInvoke() if required.
}
}
finally
{
_stoppedEvent.Set();
}
}
}
In my case I was using a BackgroundWorker ,a System.Timers.Timer and a ProgressBar in WinForm Application. What I came across is on second tick that I will repeat the BackgroundWorker's Do-Work I get a Cross-Thread Exception while trying to update ProgressBar in ProgressChanged of BackgroundWorker .Then I found a solution on SO #Rudedog2 https://stackoverflow.com/a/4072298/1218551 which says that When you initialize the Timers.Timer object for use with a Windows Form, you must set the SynchronizingObject property of the timer instance to be the form.
systemTimersTimerInstance.SynchronizingObject = this; // this = form instance.
http://msdn.microsoft.com/en-us/magazine/cc164015.aspx

Multiple async calls to UI thread to update textblock

I'm writing a fighter generation page for my game. The page is supposed to update the UI with randomized values for strength and other attributes while the fighter is being downloaded from the server.
Code so far:
public partial class FighterGenerationPage : PhoneApplicationPage
{
Fighter fighter = null;
string Code = "";
BackgroundWorker worker;
public FighterGenerationPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(FighterGenerationPage_Loaded);
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
}
void FighterGenerationPage_Loaded(object sender, RoutedEventArgs e)
{
AddFighter();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
NavigationContext.QueryString.TryGetValue("code", out Code);
if ("".Equals(Code))
if (NavigationService.CanGoBack)
NavigationService.GoBack();
base.OnNavigatedTo(e);
}
private void AddFighter()
{
WebProxy.GetInstance().AddFighter(AddFighter_Handler, Code);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
Random rand = new Random();
while (fighter == null)
{
int strength = rand.Next(100);
Dispatcher.BeginInvoke(() => { StrValue.Text = Convert.ToString(strength); });
Thread.Sleep(100);
}
Dispatcher.BeginInvoke(() => { StrValue.Text = Convert.ToString(fighter.Strength); });
}
public void AddFighter_Handler(Response response)
{
#if DEBUG
Thread.Sleep(3000);
#endif
if (response.Status.Error == false)
{
fighter = response.Fighter;
}
}
}
This code does almost do what I want, but instead of updating the UI every 0.1 sec it does it one time at start and then many times just before it sets it to the final value (fighter.Strength).
Why this behavior?
What I think is happening is this:
You are running in Debug, which means that your Thread.Sleep code in AddFighter_Handler() gets executed.
The call to AddFighter comes back very quickly.
When you Thread.Sleep() in AddFighterHandler, you are actually blocking the user thread, thus never actually calling the code you pass into BeginInvoke.
Here's the simplest thing you can do to solve this:
public void AddFighter_Handler(Response response)
{
System.Threading.ThreadPool.QueueUserWorkItem(o => {
#if DEBUG
Thread.Sleep(3000);
#endif
if (response.Status.Error == false)
{
fighter = response.Fighter;
}});
}
This will make sure you do not block the thread.
It's also probably not the right thing to - consider using the DispatcherTimer to get something like that pause you are trying to achieve..

Categories

Resources