I am already using backgroundworker.RunAsyn() to run my code on a separate thread. However I am hitting a portion where the code iterates to the next line before the previous line is completed. Should I create a separate backgroundworker to handle that? Or should I use Application.Wait() or Thread.Sleep() I am not sure the amount of time to delay and I'd rather not have my program just sitting around waiting for extra un-needed time so I am not sure which route to take. Here is a snippet of the trouble-maker.
public Form_Main()
{
InitializeComponent();
backgroundworker1.WorkerReportsProgress = true;
backgroundworker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1_ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void btnOpenRefreshSave_Click()
{
backgroundWorker1_RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Excel.Application exApp;
Excel._Workbook exBook;
Excel._Worksheet exSheet;
exBook = (Excel._Workbook)(exApp.WOrkbooks.Open("C:\\Book1.xlsx"));
exSheet = (Excel._Worksheet)(exBook.ActiveSheet);
//This is the line of code that often times takes a while
exBook.RefreshAll();
//end of trouble line
exBook.SaveAs("C:\\Updated_Book1.xlsx");
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
A few things come to mind on what to do here. You could try using something similar to the below
if (Application.CalculationState === xlDone Then
everything is finished calculating[enter link description here][1]
Another option would be (as others have suggested) changing the background refresh property. A quick scan of the workbooks could programmatically change that for you
foreach (Wrksheet ws in workbook.wss)
{
foreach (QueryTable table in ws.QueryTables)
table.BackgroundQuery = false;
}
workbook.RefreshAll();
The problem is caused because RefreshAll is running on a background thread. So basically you have your own backgroundworker running and another one you did not anticipate for.
The documentation for refreshAll says :
Objects that have the BackgroundQuery property set to true are refreshed in the background.
So you can get out of this problem only be setting that property to false. Then the refreshall would run in the context of your backgroundworker which is what your intent is.
If this still does not work, then you have to rethink your logic and look for an event of some kind that is triggered when the refresh is done. If this does not exist, then there is no solution other than a sleep, but that is not a good solution at all because you don't know how long to sleep.
Why do you want to delay something, can't you do saving your workbook on one of its events like SheetCalculate (Occurs after any worksheet is recalculated or after any changed data is plotted on a chart) and setting some flag in your code and reset that on that event (or any more relevant event)
Related
I have a basic Winform app (.Net 4.6) where the main Form consists of only one docked panel. The form acts as the screen manager and will change the screen by adding/removing custom UserControls to/from the panel.
To put it simply, I have a button in the processing screen where if you click on it, will trigger a simple callback:
private void langButton_Click(object sender, EventArgs e)
{
ActionCallback?.Invoke(UserAction.BackToTitle);
}
This will call a method in the main form and eventually:
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
Now I have checked that the CurrentUICulture changes as expected but as soon as it goes back to the UserControl, which is after the invoke line, the CurrentUICulture magically reverts back to whatever it used to be before executing the delegate! (no other code changes the culture)
What's even more baffling is similar code for my title screen works fine.
They both run on UI thread.
I have been searching online and also tried many things including locking the line that is changing the culture in case the value was cached or something but no luck.
Obviously a lot of code is omitted but that's the gist of it. I could purposely pass the culture back and assign it again but that would just be bad.
Anyone could shed some light on this behaviour and possibly suggest some solutions?
EDIT 1:
I think I have finally found the issue. The callback was something like this:
private async void HandleUserAction(UserAction action)
{
switch (action)
{
case UserAction.BackToTitle:
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
break;
//other cases omitted
}
}
I basically did a quick test and extracted this to another anonymous function but without the async :
screen.ActionCallback2 += (a) => { System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");};
and it finally works!
I am not sure if it is duplicate of the post below as I am already using .Net 4.6 or above but I have to do some more reading.
Keep CurrentCulture in async/await
I won't answer my own question yet as I am still trying to grasp why this is happening, if anyone else can explain this, feel free to do so.
EDIT 2:
I found that the issue can be reproduced with the code below
private async void button16_Click(object sender, EventArgs e)
{
//will revert back to previous value once it exits the function
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("jp");
}
private void button15_Click(object sender, EventArgs e)
{
//it will be changed to jp
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("jp");
Task.Run(() =>
{
this.Invoke((MethodInvoker)delegate
{
//this will be changed back to jp after exiting the thread, even though its same UI thread
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
});
});
}
In the end I fixed it by adding this to my app.config
<AppContextSwitchOverrides value="Switch.System.Globalization.NoAsyncCurrentCulture=true" />
Though it is still a little strange to me especially when the function button15_Click doesn't involve async.
First of all my Main is STAThread and i am not able to change this without facing problems with the rest of my code.
So, I am currently using Rapi2 To pull and push files between my Pda and Computer. Now since there is quite a bit of number crunching i would like to do this on a separate thread. First wat i do is create an RemoteDeviceManager and then make an Event Handler for when a device connects.
public void Initialize()
{
_deviceManager = new RemoteDeviceManager();
_deviceManager.DeviceConnected += DeviceConnected;
}
As you can see when my device connects it triggers DeviceConnected.
This is the class that i end up pulling and pushing a database and do some number work.
private void DeviceConnected(object sender, RemoteDeviceConnectEventArgs e)
{
if (e.Device == null) return;
... (unimportant code)
}
Now the problem here is that i would want to run the code inside DeviceConnected in a new thread but i am unable to access e inside the new thread since it was initialized outside that thread
So now wat i tried was make a new thread before calling Initialize.
public Watcher()
{
_dataThread = new Thread(Initialize);
_dataThread.IsBackground = true;
_dataThread.Name = "Data Thread";
_dataThread.SetApartmentState(ApartmentState.MTA);
_dataThread.Start();
}
But the thread dies and thus never fires my event handler.
I tried many different ways to make it work or keep my thread alive but without any success. I hope someone here is able to give me some hints.
My project have a MainForm, i show F_Insert and set MdiParent for MainForm
F_Insert f = new F_Insert();
f.MdiParent = this;
f.Show();
In F_Insert, i put a button with CLick event like this
private void btn_Add_Click(object sender, EventArgs e)
{
//Insert data to SQL
}
Besides, i want to auto upload data that inserted from F_Insert every 5 second
I use System.Timer.Timer and set it to Thread in MainForm_Load
Thread t1 = new Thread(new ThreadStart(Timerss)); //In MainFormLoad event
t1.Start();
public void Timerss()
{
System.Timers.Timer timer = new System.Timers.Timer(5000);
timer.Elapsed += Timer_Insert_Tick;
timer.AutoReset = true;
timer.Start();
}
private static void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
//code auto upload data to server here
//Data get from Sql Local to upload SQL in Server
}
The problem is it's not working good. I feel when i insert data form F_Insert, data is affected by Timerss thread that i start in MainForm load.
The simple way to show you my problem: when i split two work (Insert
and upload) into 2 difference work, it working good, it's mean i'm
insert data complete and then, i upload data, it will working good.
But when i insert data and data auto upload by timer in the same time,
i see some error that: conection sql close or open error, no data get
from F_Insert, sometime it get duplicate data (old data)
Please suggeted me some idea for this problem. Sorry but i'm newbie in thread. Thank you !!!
Well depending on what you trying to do this code should be modified but i hope it'll give you starting point to work with.
First of all let's create static field:
static volatile bool isDataChanged;
Keyword volatile makes this bool thread-safe, it means that this field always holds latest (and therefore correct) value when it is accessed by any thread in multi-thread environment).
We need this field to hold bool value that is used later to check whether the data was modified or not.
Assuming that data is modified inside click event handler, we should set this flag to true :
private void btn_Add_Click(object sender, EventArgs e)
{
// Data is modified in UI thread
isDataChanged = true;
}
Then let's assume that in Timer tick event we should upload the latest data to database (data is located in UI thread and could change in time span in between two tick events).
First of all we check if there is any changes to our data and if there's not we just exits the method. If changes was done we need to upload them to DB and in order to do so we have to deal with the fact that data in Timer thread could very well not be the same as data in our UI thread.
Let's create local variable that will hold correct data that we fetch from UI thread and use this.Invoke() to invoke Func<object> delegate on UI thread. The method that is attached to delegate returns instance of correct data retrieved from UI thread as object. We cast it explicitly to the type that our data is (usually it's one of collection types like List<T> or Dictionary<T1, T2>) and use this data to upload it to the DB.
After that, because our data in DB is the correct one, we change flag isDataChanged to false.
private void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
if(!isDataChanged) return;
// A very important line. It gets data from UI thread before uploading it
// Change DataType with your data Type and dataToUpload with data instance
DataType data = (DataType)this.Invoke(new Func<object>(() => dataToUpload));
//use data to upload your data to server
isDataChanged = false;
}
P.S.
Also it is better to place reference to our Timer in outer scope (so it can be accessed from anywhere inside the form)
public partial class MyForm : Form
{
...
System.Timers.Timer timer;
public void Timerss()
{
timer = new System.Timers.Timer(5000);
}
...
}
Part of my program uses an event handler for the receive data of my serial port. The idea is when data is received that the text received is then added to the textbox (rx). I did not used to have this problem but something has changed and I can't figure out what. So now I am re-examining the way this is handled.
During the form load of my winform the last thing I do is
if (!serialPort1.IsOpen)
{
serialPort1.Open();
serialPort1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
Then I have the event handler
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
string indata1 = serialPort1.ReadExisting();
// rx.Text = " "; accidentally posted this. it was from trial and error.
rx.AppendText(Environment.NewLine + indata1);
}
When I run the program it stops at the rx.AppendText(Environment.NewLine + indata1); and gives the error
invalidoperationexception was unhandled: Control "accessed from a
thread other than the thread it was created on.
From what I have been able to read suggests that I need to use invoke or BeginInvoke.
I have never had problems appending the text before so now I can't understand why it's a problem. Also from what I have been reading on invoking i just don't understand it.
Can someone help me understand how to use the invoke instance for my situation? or perhaps show me another way of appending the text box?
Usually the exception you're seeing occurs when you run in debug mode, and if you run your application in release mode, you're unlikely to see the exception.
However, it is best to use invoke, as you have read. Something like this:
private delegate void RefreshTextBox();
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) {
//this event is raised in an event separate from UI thread,
//so InvokeRequired must be checked and Invoke called to update UI controls.
if (this.InvokeRequired) {
RefreshTextBox d = new RefreshTextBox(RefreshTextBoxResults);
Invoke(d);
} else {
RefreshTextBoxResults();
}
}
private void RefreshTextBoxResults() {
string indata1 = serialPort1.ReadExisting();
rx.Text = " ";
rx.AppendText(Environment.NewLine + indata1);
}
The first time you see this invoke stuff, it's nearly impossible to follow, but take a close look and give it some time and it will make sense. Promise. :)
Updates in GUI applications should only be done on the GUI thread. Another thread attempting to update GUI components directly will result in either the error you described or in seemingly random behavior.
The role of Invoke & friends is to enable a secondary thread to safely forward GUI updates to the GUI thread, which will then process them from a queue.
In your case (assuming WinForms here):
rx.BeginInvoke(
(Action)(() =>
{
rx.AppendText(Environment.NewLine + indata1);
}));
BeginInvoke is asynchronous, so the thread calling it will not wait for the actual updates to be processed before moving on, while Invoke is synchronous.
This has been asked before here, but the answer there was simply "use BackgroundWorker", and I'm asking if there is a complete code sample available.
I'd like to create a standard AutocompleteTextBox that works with a timer, such that there is only one BackgroundWorker working on searching - if the user entered a few more keystrokes, but the old search is still running - that search shall be canceled gracefuly (via CancelAsync), and as soon as its canceled the new search will begin.
This is not so trivial to implement - are there any code samples of this?
I doubt you'll find a code sample that helps you with the specific issues you're talking about here. Here's how I'd do this. None of this code is tested, so beware of stupid bugs.
First, subclass TextBoxBase and add two basic methods to implement the search logic, with the following signatures:
private IEnumerable<string> PerformSearch(string text)
private DisplayResults(IEnumerable<string> results)
Add a private BackgroundWorker field named Worker to the class and set its DoWork and RunWorkerCompleted events to event handlers named Worker_DoWork and Worker.RunWorkerCompleted.
Override OnTextChanged:
public override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
// if we're already cancelling a search, there's nothing more to do until
// the cancellation is complete.
if (Worker.CancellationPending)
{
return;
}
// if there's a search in progress, cancel it.
if (Worker.IsBusy)
{
Worker.CancelAsync();
return;
}
// there's no search in progress, so begin one using the current value
// of the Text property.
Worker.RunWorkerAsync(Text);
}
The Worker_DoWork event handler is pretty simple:
private void Worker_DoWork(object sender,
RunWorkerCompletedEventArgs e)
{
e.Result = PerformSearch((string) e.Argument);
}
The Worker_RunWorkerCompleted event handler looks something like this:
private void Worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
// always check e.Error first, in case PerformSearch threw an exception.
if (e.Error != null)
{
// in your version, you want to do real exception handling, not this.
throw e.Error.InnerException;
}
// if the worker was cancelled, it's because the user typed some more text, and
// we want to launch a new search using what's currently in the Text property.
if (e.Cancelled)
{
Worker.RunWorkerAsync(Text);
return;
}
// if the worker wasn't cancelled, e.Result contains the results of the search.
DisplayResults((IEnumerable<string> e.Result);
}
Note that DisplayResults should test any assumption it makes about the state of the text box. The text box may have been visible or enabled when the user launched the search and not be visible or enabled now, for instance. What happens if you use this text box in a modal dialog and the user cancels the dialog while the search is running?
Note also that if you have multiple instances of this control in your application, each one will have a different BackgroundWorker, so it's important that the PerformSearch method be thread-safe. If it's not, it will have to implement locking, so that if you launch a search in one text box it blocks and waits if another text box is currently using the shared resource.
I suggest using the AutoComplete feature in System.Windows.Forms.TextBox. You can customize it and build your completion stuff around this.
NOTE: AutoComplete feature is only available from .NET 2.0