In a legacy app we have a single winform that locks up while executing code on a button click...well it doesn't lock up it just "stops responding" until the code executes.
What is the best (least amount of code and working) way to wrap the code below into a separate thread and show a loading window (frmLoading) while it executes? I know this should be relatively simple, but I have tried a few different things that haven't quite worked out.
private void btnSynch_Click(object sender, EventArgs e)
{
btnSynch.Enabled = false;
if(chkDBSynch.Checked)
PerformDBSyncronization();
if(chkAppSynch.Checked)
PerformApplicationSyncronization();
btnSynch.Enabled = true;
}
EDIT:
Ok, I should have mentioned have tried backgroundworker, but I figured out where I was tripping up....This code would execute and the loading form was getting thrown behind the main form which is why I thought it wasn't working. Can Anyone tell me how to prevent this from happening?
private void btnSynch_Click(object sender, EventArgs e)
{
btnSynch.Enabled = false;
frmLoading loadingWindow = new frmLoading();
loadingWindow.Show();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, args) =>
{
Thread.Sleep(6000); //TODO:just for testing
if(chkDBSynch.Checked)
PerformDBSyncronization();
if(chkAppSynch.Checked)
PerformApplicationSyncronization();
};
bw.RunWorkerCompleted += (s, args) =>
{
loadingWindow.Close();
};
bw.RunWorkerAsync();
btnSynch.Enabled = true;
}
I feel silly...The loading form was popping up behind the the main form which is why I thought it wasn't opening. I set the TopMost property to true and everything works as expected.
Edit:
Here is a good explanation why my form was popping up behind my window, I just wasn't expecting the thread that was opening the window to not use the main form as the owner.
Why use a owner window in MessageBox.Show?
I ended up with my code looking like
frmLoading loadingWindow = new frmLoading();
loadingWindow.Show(this);
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, args) =>
{
this.Invoke(new MethodInvoker(() => this.Enabled = false));
if(chkDBSynch.Checked)
PerformDBSyncronization();
if(chkAppSynch.Checked)
PerformApplicationSyncronization();
};
bw.RunWorkerCompleted += (s, args) =>
{
loadingWindow.Close();
this.Invoke(new MethodInvoker(() => this.Enabled = true));
};
bw.RunWorkerAsync();
Use a BackgroundWorker for this purpose.
Related
/Disclaimer, this is my first time working with WPF and with multi-threading, so bear with me if I am making some big mistakes/
So I have an application with a tabcontrol. In one of the Tabs I intend to load in a visio file via the usual windows form host + Visio viewer activeX control etc... And it works perfectly. The only issue is that when I load in the document the UI freezes for 20 seconds (as I am loading in rather huge files). As I read this is because my application is running on a simple thread. So I was trying to implement a background worker to keep the UI reactive while the background thread is running.
When I instantiate my UserControl then I add to the Tab (Initialpath is the filepath of the visio file):
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync(initialpath);
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
DiagramView UCworker;
UCworker = new DiagramView((string)e.Argument);
e.Result = UCworker;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UC = (DiagramView)e.Result;
this.Host.Child = UC;
}
And When it creates the new DiagramView:
public DiagramView(string path)
{
InitializeComponent();
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.Load(path);
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
}
And for this line:
this.viewer = new AxVisioViewer.AxViewer();
I get: : 'ActiveX control 'f8cf7a98-2c45-4c8d-9151-2d716989ddab' cannot be instantiated because the current thread is not in a single-threaded apartment.'
I read that the backgroundworker is not capable to modify the UI elements (correct me if I am wrong)
I also saw this thread: Single-threaded apartment - cannot instantiate ActiveX control
But I am not sure how to implement it (this STA apartment state business) and when I tried, the visio viewer simple crashed when trying to open the document.
I need some guideline how to approach this issue, cause my goal would be to having a loading page to display with an animation until the Document is finished loading/rendering so I can display it.
Thank you in advance for the answers.
UPDATE: I also tried the following approach:
public partial class TabDiagramView : UserControl, INotifyPropertyChanged
{
public delegate void DisplayVisio(DiagramView view);
public DisplayVisio DelegateM;
DiagramView UC;
public TabDiagramView()
{
InitializeComponent();
DelegateM = new DisplayVisio(DisplayV);
Thread t = new Thread(RT);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
}
#region Thread
private void RT()
{
DiagramView UCworker;
UCworker = new DiagramView(initialpath);
Dispatcher.Invoke(DelegateM, UCworker);
}
private void DisplayV (DiagramView DiagV)
{
UC = DiagV;
this.Host.Child = DiagV;
}
But in this case I get the following message on UC and the this.Host.chilld=DiagV when I am in the DisplayV method: System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
It's simply impossible to load a UI control from another thread. However you could solve the freezing problem if the library had provided a thread-safe approach (e.g. AxViewer.LoadAsync) so the only way to make viewer.Load bearable for the user is to delay viewer.Load operation until the window is loaded:
string _path;
public DiagramView(string path)
{
InitializeComponent();
_path = path;
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
this.Loaded += Viewer_Loaded;
}
private void Viewer_Loaded(object sender, RoutedEventArgs e)
{
viewer.Load(_path);
}
And more importantly, delete the background worker code. It doesn't do any good here.
I'm trying to load a large amount of data from Entity Framework. The request takes about a minute and blocks the page load. Since this becomes unreasonable for larger queries, I thought I would try to use a background thread or something similiar. After a little while of researching about this I found something called BackgroundWorker. I have tried to implement this but its not working.
Here's the code I have:
void Page_LoadComplete(object sender, EventArgs e)
{
InitializeBackgroundWorker();
}
private void InitializeBackgroundWorker()
{
bw = new BackgroundWorker {WorkerReportsProgress = true};
bw.DoWork += (sender, e) => e.Result = (List<object>)e.Argument;
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync(doSomething());
bw.RunWorkerCompleted += (sender, e) =>
{
AjaxWaitBox.Text = "Completed";
};
}
private readonly Func<List<object>> doSomething = () =>
{
var list = ObjectFactory.Container.GetInstance<IActivityRepository>().GetAllActivitiesNotFiltered(ContentReference.RootPage);
var count = list.Count;
int i = 0;
foreach (var item in list)
{
i++;
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> " + percentage);
bw.ReportProgress((int)percentage);
}
return list;
};
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
AjaxWaitBox.Text = e.ProgressPercentage.ToString();
}
At first I had the call to InitializeBackgroundWorker() in the OnInit method. The page then loaded for about a minute and then the value of my textbox, which was intended to display the progress of the query, was: "completed". I then moved the call to Page_LoadComplete but the result was still the same. What am I missing here? Do I need to include async/await operators as well or what?
BackgroundWorker only useful for freeing up the UI thread. Since you are on ASP.NET, you do not have a UI thread.
ASP.NET works with HTTP requests and responses. The "page" abstraction is (unfortunately) made to look like a UI framework, but it is not.
So, you need to think about your problem in terms of HTTP requests and responses, since that is what is actually going on. There is only one response per request, so there's no way to "send a page-without-data response" and then later change it to "send a page-with-data response".
Instead, what you have to do is send a page-without-data response and then have that page use a technology like AJAX (or UpdatePanel) to issue a separate request for the actual data and fill in the page with that.
You are calling the doSomething() synchronously.
You should do something like this (not tested):
private void InitializeBackgroundWorker()
{
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker { WorkerReportsProgress = true };
bw.DoWork += (sender, e) => e.Result = doSomething();
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();
bw.RunWorkerCompleted += (sender, e) =>
{
AjaxWaitBox.Text = "Completed";
};
}
We've made an Application with a MainWindow called MV.
When main starts it launches our StartProgram method as a BackgroundWorker and the Application.Run(MW);
MainWindow MW = new MainWindow();
BackgroundWorker.DoWork += (obj, e) => StartProgram(MW);
BackgroundWorker.RunWorkerAsync();
Application.Run(MW);
In StartProgram we create instances of Patient, which we want to show in our listView1.
We do this by calling this method, which is in MW:
public void SetListSource(Patient p)
{
ListViewItem item = new ListViewItem("woohoo");
item.SubItems.Add("a");
listView1.Items.Add(item);
}
StartProgram stalls when it reaches listView1.Items.Add(item);
Our guess is, that it waits for MW (MainWindow), but we can't figure out how to fix it.
We have a button in MW, that does somethis similar, except it only sends "1" and "a" to the listView1.
private void Sort_Button_Click(object sender, EventArgs e)
{
ListViewItem item = new ListViewItem("1");
item.SubItems.Add("a");
listView1.Items.Add(item);
}
Does anybody know how to make SetListSource(...) work as Sort_Button_Click(...)?
EDIT
Solved with Invoke
You can't modify your GUI directly from another thread. You need to use a delegate and invoke your control. In your thread you have to do:
CONTROL.Invoke(new Action(() =>
{
CONTROL.Items.Add(item);
}
));
Source:
BackgroundWorker multithread access to form
You can use Invoke, but that's probably unnecessary. You can use BackgroundWorker.RunWorkerCompleted event instead:
BackgroundWorker.DoWork +=
(s, e) =>
{
e.Result = DoAllTheComplicatedWork();
}
BackgroundWorker.RunWorkerCompleted +=
(s, e) =>
{
// Back on the UI thread, we can do whatever we want
listView1.Items.Add(((SomeDTO)e.Result).Patient);
}
BackgroundWorker.RunWorkerAsync();
Or, you can do the whole thing using await:
MW.Load += async (s, e)
{
var result = await Task.Run(() => SomeCPUWork());
listView1.Items.Add(((SomeDTO)e.Result).Patient);
}
The key point is, you really want a separation between the UI and whatever you need to do in background. I definitely wouldn't pass a form (or any other control) to any method that's supposed to be executed on a different thread.
I have been trying to figure out a problem with a background load I do on startup. The application runs totally fine but when its closed, it hangs forever. I assumed this was a threading issue. I have narrowed it down to the following code. I have been googling around but not come across anything which fits the problem I am having, can anybody elaborate on the thread safety here?
I assumed that considering the loading screen is closed when the worker is completed ( m_LoaderWindow.Close(); ) that it wouldn't be problematic.
This code doesn't work
m_LoaderWindow = new LoadingWindow();
m_BackgroundWorker = new BackgroundWorker();
OnProgressDelegate = m_BackgroundWorker.ReportProgress;
m_BackgroundWorker.WorkerReportsProgress = true;
m_BackgroundWorker.ProgressChanged += (object sender, ProgressChangedEventArgs arg) =>
{
LoaderWindow.Context.Progress = arg.ProgressPercentage;
};
m_BackgroundWorker.DoWork += MBackgroundWorkerOnDoWork;
m_BackgroundWorker.RunWorkerCompleted += MBackgroundWorkerOnRunWorkerCompleted;
m_BackgroundWorker.RunWorkerAsync();
m_LoaderWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
m_LoaderWindow.Owner = Application.Current.MainWindow;
m_LoaderWindow.ShowDialog();
This code works (but obviously no loading screen)
m_BackgroundWorker = new BackgroundWorker();
OnProgressDelegate = m_BackgroundWorker.ReportProgress;
m_BackgroundWorker.WorkerReportsProgress = true;
m_BackgroundWorker.ProgressChanged += (object sender, ProgressChangedEventArgs arg) =>
{
LoaderWindow.Context.Progress = arg.ProgressPercentage;
};
m_BackgroundWorker.DoWork += MBackgroundWorkerOnDoWork;
m_BackgroundWorker.RunWorkerCompleted += MBackgroundWorkerOnRunWorkerCompleted;
m_BackgroundWorker.RunWorkerAsync();
Here is the worker completed code
private void MBackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
m_LoaderWindow.Close();
}));
}
Ok it wasn't related to anything with the threading. In my ViewModel I was doing this:
public LoadingWindow m_LoaderWindow = new LoadingWindow();
And then I was allocating it AGAIN in the main thread.
Although I can't explain why this causes it to hang on exit?
I want to:
Show a form with a textbox.
Run an external program (notepad.exe for ease of example).
Continue to allow the user to enter data into the form textbox whilst notepad is running.
Run some more (continue) native form code when notepad closes. This will update the form, amongst other things.
I'm having problems making this happen. I'm aware of a multitude of posts about this similar issue, but haven't found a solution that works for me.
I have tried:
Doing a waitforexit, but this of course blocks the UI and users cannot enter data.
Attempting an asynchronous process call, where another method is called when this process is completed. This causes a problem where the new method is called from another thread and can't update the form.
Doing a wait/sleep loop in the UI, but again this will naturally block the UI.
What would be the neatest, and simplest solution for a simple Windows Form program? There are no extra classes used, and all code is in the Form1 class.
The Process class fires an Exited event when the process exits. You can add a handler to that event to execute code when the process exits without blocking the UI thread:
process.EnableRaisingEvents = true;
process.Exited += (s, args) => DoStuff();
Alternatively you could create a Task that represents the completion of the process to leverage the TPL for asynchrony:
public static Task WhenExited(this Process process)
{
var tcs = new TaskCompletionSource<bool>();
process.EnableRaisingEvents = true;
process.Exited += (s, args) => tcs.TrySetResult(true);
return tcs.Task;
}
This would allow you to write:
await process.WhenExited();
UpdateUI();
Here you go:
void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
var p = Process.Start("notepad.exe");
p.WaitForExit();
}).ContinueWith(antecedant => { MessageBox.Show("Notepad closed"); });
}
Here is my favorite way to do something like this with a BackgroundWorker. This has the advantage of the RunWorkerCompleted callback being on the main thread, so it can interact with the UI.
public partial class Form1 : Form
{
...
private BackgroundWorker wrk;
private void button1_Click(object sender, EventArgs e)
{
wrk = new BackgroundWorker();
wrk.DoWork += (s, ea) => { /*Create your process and wait here*/ };
wrk.RunWorkerCompleted += (s, ea) => { textBox1.Text = "Finished"; };
wrk.RunWorkerAsync();
}
}
You should start process in BackgroundWorker so you can catch complete event on same thread.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate {
Process proc = Process.Start("YOUR-PROCESS-PATH");
proc.Start();
proc.WaitForExit();
}
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
then catch the worker ended event on called thread;
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Do your thing o UI thread
}