Sorry for the long post, but I tried to explain the problem very detailed so that no confusion should arise. The last sentence contains the actual question.
I'm programming a multi-thread application with C#/.NET.
The application consists of a main window, which visualizes data, coming from a pressure sensor. The sensor data is acquired in an own thread.
The data is also logged in an instance of class ListView:
There is the possibility to save the logged data to file on disk via a "Save" button (should open an instance of .NET class SaveFileDialog).
This SaveFileDialog is also running in an own thread.
Now there's a problem when calling the method SaveFileDialog.ShowDialog():
System.InvalidOperationException was unhandled
Message="Cross-thread operation not valid: Control 'tlpMain' accessed from a thread other than the thread it was created on."
Source="System.Windows.Forms"
The problem arises because the owner (the main window) of the SaveFileDialog is running in another thread.
Here's the code, which creates the thread for the SaveFileDialog():
private void bSave_Click(object sender, EventArgs e)
{
Thread saveFileDialog = new Thread(OpenSaveFileDialog);
saveFileDialog.SetApartmentState(ApartmentState.STA);
saveFileDialog.Start();
}
Code for method OpenSaveFileDialog():
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text Files (*.txt)|*.txt|CSV (*.csv)|*.csv|All Files (*.*)|*.*";
saveFileDialog.FilterIndex = 0;
/* Call "ShowDialog" with an owner ("this.Parent") to achieve, so that
* the parent window is blocked and "unclickable".
*
* Danger of an "InvalidOperationException" because "this.Parent" control
* is running (was created) in another thread.
* But "this.Parent" should not be modified by this method call.
*/
DialogResult pressedButton = saveFileDialog.ShowDialog(this.Parent);
...
The InvalidOperationException is only thrown/displayed when running the application with Visual Studio's debugger. It is no problem - so far - when running the application "normally".
But I would like to avoid this problem.
I tried to build a wrapper method (SaveFileDialog):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
SaveFileDialog(saveFileDialog, this.Parent);
}
Wrapper method :
private void SaveFileDialog(SaveFileDialog saveFileDialog, Control owner)
{
if (owner.InvokeRequired)
BeginInvoke(new dSaveFileDialog(SaveFileDialog), new object[] { saveFileDialog, owner });
else
{
DialogResult pressedButton = saveFileDialog.ShowDialog(owner);
...
This leads to a TargetInvocationException although the Main() method is labeled with [STAThreadAttribute]:
InnerException: System.Threading.ThreadStateException
Message="Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process."
Source="System.Windows.Forms"
Does anybody have a clue how to open the SaveFileDialog in a way, so that the main window will be blocked ("unclickable") without having the (thread) trouble?
Thank you.
The cross-thread exception you get during debugging is a Managed Debugging Assistant. They are not normally active outside of the debugger. That explains why you do not see when you run the application outside of Visual Studio.
It looks like you have discovered on your own that you simply cannot do anything to a UI element from a thread other than the main UI thread. You use the ISynchronizeInvoke methods, namely Invoke or BeginInvoke, to marshal the execution of an operation onto the UI thread so that you can safely access UI elements.
I still see a problem with your code though. In the OpenSaveFileDialog method, which is running on the worker thread, you are calling the constructor for SaveFileDiaglog which, of course, is a UI element. You just cannot do this. It is worth repeating. You cannot do anything to a Form or Control from a worker thread. That includes calling the constructor.
Sorry for the late reply.
First of all thank you for your quick and helpful responses.
The tip that's not possible
do anything to a Form or Control from
a worker thread
helped me a lot.
I usually not doing GUI programming for Microsoft's Windows and so I'm not so familiar with it.
So I reconsidered the previous source code because I wanted to solve the actual problem
(not doing GUI things from a worker thread) and would like to have a clean and logical code structure.
Therefore I've read in the topics of Window's Component Object Model (COM) and the used threading model:
What is COM:
http://www.microsoft.com/com/default.mspx
Understanding and Using COM Threading Models:
http://msdn.microsoft.com/en-us/library/ms809971.aspx
Understanding The COM Single-Threaded Apartment Part 1:
http://www.codeproject.com/KB/COM/CCOMThread.aspx
Now the code looks like this:
The main window ("UI thread") is started in ApartmentState STA
...
ThreadStart threadStart = delegate { RunMainWindow(mainWindow); };
Thread mainWindowThread = new Thread(threadStart);
mainWindowThread.SetApartmentState(ApartmentState.STA);
mainWindowThread.Start();
...
"Save" button event handler (main window):
private void bSave_Click(object sender, EventArgs e)
{
OpenSaveFileDialog();
}
Method "OpenSaveFileDialog" (main window):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
DialogResult pressedButton = saveFileDialog.ShowDialog();
...
}
There is still space for optimizations (for sure), but I'm comfortable with this - preliminary - result.
So thanks a lot for your help.
Follow this microsoft blogpost: http://blogs.msdn.com/b/smondal/archive/2011/05/11/10059279.aspx
Just two methods and you are done!
Related
using System.Windows.Forms;
public class App
{
[STAThread]
public static void Main()
{
string fname;
using (var d = new OpenFileDialog())
{
if (d.ShowDialog() != DialogResult.OK)
{
return;
}
fname = d.FileName;
}
//Application.ExitThread();
for (; ;)
;
}
}
The above code shows me a file dialog. Once I select a file and press open, the for loop is executed, but the (frozen) dialog remains.
Once I uncomment Application.ExitThread() the dialog disappears as expected.
Does that work as intended? Why doesn't using make the window disappear? Where can I find more info about this?
You have discovered the primary problem with single-threaded applications... long running operations freeze the user interface.
Your DoEvents() call essentially "pauses" your code and gives other operations, like the UI, a chance to run, then resumes. The problem is that your UI is now frozen again until you call DoEvents() again. Actually, DoEvents() is a very problematic approach (some call it evil). You really should not use it.
You have better options.
Putting your long running operation in another thread helps to ensure that the UI remains responsive and that your work is done as efficiently as possible. The processor is able to switch back and forth between the two threads to give the illusion of simultaneous execution without the difficulty of full-blown multi-processes.
One of the easier ways to accomplish this is to use a BackgroundWorker, though they have generally fallen out of favor (for reasons I'm not going to get into in this post: further reading). They are still part of .NET however and have a lower learning curve then other approaches, so I'd still suggest that new developers play around with them in hobby projects.
The best approach currently is .NET's Tasks library. If your long running operation is already in a thread (for example, it's a database query and you are just waiting for it to complete), and if the library supports it, then you could take advantage of Tasks using the async keyword and not have to think twice about it. Even if it's not already in a thread or in a supported library, you could still spin up a new Task and have it executed in a separate Thread via Task.Run(). .NET Tasks have the advantage of baked in language support and a lot more, like coordinating multiple Tasks and chaining Tasks together.
JDB already explained in his answer why (generally speaking) your code doesn't work as expected. Let me add a small bit to suggest a workaround (for your specific case and for when you just need to use a system dialog and then go on like it was a console application).
You're trying to use Application.DoEvents(), OK it seems to work and in your case you do not have re-entrant code. However are you sure that all relevant messages are correctly processed? How many times you should call Application.DoEvents()? Are you sure you correctly initialize everything (I'm talking about the ApplicationContext)? Second problem is more pragmatic, OpenFileDialog needs COM, COM (here) needs STAThread, STAThread needs a message pump. I can't tell you in which way it will fail but for sure it may fail.
First of all note that usually applications start main message loop using Application.Run(). You don't expect to see new MyWindow().ShowDialog(), right? Your example is not different, let Application.Run(Form) overload creates the ApplicationContext for you (and handle HandleDestroyed event when form closes which will finally call - surprise - Application.ExitThread()). Unfortunately OpenFileDialog does not inherit from Form then you have to host it inside a dummy form to use Application.Run().
You do not need to explicitly call dlg.Dispose() (let WinForms manage objects lifetime) if you add the dialog inside the form with the designer.
using System;
using System.Windows.Forms;
public class App
{
[STAThread]
public static void Main()
{
string fname = AskForFile();
if (fname == null)
return;
LongRunningProcess(fname);
}
private static string AskForFile()
{
string fileName = null;
var form = new Form() { Visible = false };
form.Load += (o, e) => {
using (var dlg = new OpenFileDialog())
{
if (dlg.ShowDialog() == DialogResult.OK)
fileName = dlg.FileName;
}
((Form)o).Close();
};
Application.Run(form);
return fileName;
}
}
No, you don't have to call Application.ExitThread().
Application.ExitThread() terminates the calling thread's message loop and forces the destruction of the frozen dialog. Although "that works", it's better to unfreeze the dialog if the cause of the freeze is known.
In this case pressing open seems to fire a close-event which doesn't have any chance to finish. Application.DoEvents() gives it that chance and makes the dialog disappear.
I'm working on a project that is designed to play both audio and video files through a WPF Window through a MediaElement. This is the xaml for the window:
<Window x:Class="HomeSystem_CSharp.MediaWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MediaWindow" MinHeight="480" MinWidth="720" WindowStyle="None" ResizeMode="NoResize" Visibility="Visible" Cursor="None">
<Grid Background="Black">
<MediaElement LoadedBehavior="Manual" HorizontalAlignment="Stretch" Name="video" VerticalAlignment="Stretch" Cursor="None" MinHeight="480" MinWidth="720"/>
</Grid>
</Window>
This creates the window with no borders, that I plan on full-screening in the future. For now though, I want more room on my desktop. Here is my code for controlling my MediaElement:
private bool playing = false;
public MediaWindow(string dir)
{
InitializeComponent();
video.Source = new Uri(dir);
play();
}
public void play()
{
if (playing)
return;
if (!this.IsVisible)
this.Show();
video.Play();
playing = true;
}
This MediaWindow is created outside of the object, just by a simple MediaWindow mw = new MediaWindow("C:\\test.mp4");
No matter how i've moved stuff around in my code, upon launch EVERY time the GUI is unresponsive, but sound plays. I can hear the video in the background, but there is a broken window on my screen. Just a black box.
The biggest issue is that just the other day it was working fine, and suddenly it broke, and I have no clue what happened. I'm kinda new to c#, so I dont know a TON about what's going on, but I've worked with java for several years so I'm not totally new. Can anyone point out what I'm doing wrong? i can provide any other details but I think i got everything necessary to answer. Thank you for any help, this has been bothering me all day with no fix!
EDIT: Turns out, if I use
public void play()
{
if (playing)
return;
//if (!this.IsVisible)
// this.Show();
video.Play();
new Application().Run(this);
playing = true;
}
instead, it will run the GUI. However, that hangs up the console. Originally I fixed that hang up by using this.Show(), but now that's not working. I know that moving the whole project into a WPF project would fix this, however I'm really trying not to for other reasons. Only win32 for now. Any ideas why this is happening and how to fix it? I do have [STAThread] over my main function if that makes a difference.
EDIT 2:
This video file I'm playing is movie length, and runs perfectly in any other software to prevent that from being an issue with development. As for the MediaWindow creation. What I did is made a win32 console project and set up the user commands there. I then made a new WPF project, and created an xaml gui window. I took those code files, and copied them into the win32 project, and call it to launch in the main method with the MediaWindow mw = new MediaWindow("C:\\test.mp4"); I did it this way because for now I'm trying to keep away from using a pure WPF application, and because I'm kinda new to C# so I wasnt sure how to create the window I wanted without my copy paste method.
No matter how i've moved stuff around in my code, upon launch EVERY time the GUI is unresponsive, but sound plays.
I've managed to reproduce this. One important thing missing in your description is the exact way you create and show the window in your main() method. For example, the following freezes the video leaving the sound playing:
[STAThread]
static void Main(string[] args)
{
var w = new MediaWindow();
w.Show();
Console.ReadLine();
}
The next one "freezes" the console until you close the window:
[STAThread]
static void Main(string[] args)
{
var w = new MediaWindow();
w.ShowDialog();
Console.ReadLine();
}
And this gives you both working:
static void Main(string[] args)
{
var thread = new Thread(ShowMediaWindow);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (true) // Just to test console working
{
Console.Write("\r" + DateTime.Now);
Thread.Sleep(100);
}
}
static void ShowMediaWindow()
{
new MediaWindow().ShowDialog();
}
As you can see, the console and the WPF window simply can't work properly in a single thread.
The window class is as simple as this, by the way (the XAML is mostly the same as yours):
public partial class MediaWindow : Window
{
public MediaWindow()
{
InitializeComponent();
video.Source = new Uri(#"C:\video\test.asf");
Play();
}
public void Play()
{
video.Play();
}
}
I guess that'll do for showing a video player window from console.
OK, the whole hybrid console/GUI thing is a new one on me but I'm just going to assume there's a real need to do things that way.
The Application.Run method doesn't return until the application closes. That's why your console is locked up.
Don't create the Application object inside the window. Do it externally. Also, spawn another thread to kick off video playback. This will leave your console responsive.
I'm not gonna get heavy-duty into describing threading and delegates and so forth... you can look that up if you want. I'm just gonna go over what you need to do for this specific example. Somewhere in the class that launches the video, but not in a method, define a delegate type like this:
delegate void LaunchVideo(String s);
A delegate is essentially kind of a pointer to a function with a certain definition of return value and parameters. Here we've defined the delegate type as a function that takes a String parameter and returns nothing. Next, at the point in your code where you want to play the video, do this:
LaunchVideo lv = new delegate(String vidfile)
{
Application app = new Application();
app.Run(new MediaWindow(vidfile));
};
IAsyncResult result = lv.BeginInvoke( "C:\\vid.mp4", myVideoCompleted, null );
This creates the delegate variable and points it at an anonymous function that creates the app and launch video playback. Then it calls the delegate's BeginInvoke method, which is part of the basic delegate class. This spawns a new thread running in the function pointed to by the delegate.
Note that calling Application.Run with a window parameter like this will open the window but it won't call the play() method. You may want to move that code to the constructor, or add a call to it in the constructor.
Be aware that your main thread cannot safely call methods in objects created in the invoked thread unless you use the lock function to make things thread safe.
If you need "open" and "play" to be separately controlled events which are both invoked by the console then you'll have to figure out a means to pass messages from the console thread to the window thread.
The parameter list for BeginInvoke always starts off with whatever parameters are expected by the function you're invoking. So in this case, that's the string with the video filename. Next is the name of a callback function which will be called when the invoked function exits. It's a void function that takes an AsyncResult parameter, like this:
void myVideoCompleted(AsyncResult result)
{
// Do whatever here... Be aware this is still on the other thread
}
You can use 'null' instead of a function name, if you don't need anything called at the end. Be aware that if you do use a callback function, it runs on the new thread started by BeginInvoke, not the thread that called it.
The last parameter to BeginInvoke is an object that will be passed through to the callback function via the AsyncState member of the AsyncResult parameter. You can pass 'null' if you're not using a callback or if you have no parameters which will be needed by the callback.
You can also call the EndInvoke method of the delegate to get back any results that may've been returned by the function. However, be aware that this will block if the invoked function isn't finished yet. In this case you have no results to worry about.
I'm trying to create GUI program that generates HTML invoices, and sends them for printing. I have this working. However, now I want to introduce threading.
I have a form with a BackgroundWorker. The Background worker runs this code:
#region BackGroundWorker
private void bg_htmlGeneration_DoWork(object sender, DoWorkEventArgs e)
{
//SOME MORE CODE..
foreach (XElement ele in Lib.GetInvoiceElement(inv, ico.Supplier))
{
PrintDocument(Lib.CreateHTMLFile());
}
}
#endregion
public void PrintDocument(string fileName)
{
var th = new Thread(() =>
{
WebBrowser webBrowserForPrinting = new WebBrowser();
webBrowserForPrinting.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(PrintDocumentHandler);
webBrowserForPrinting.Url = new Uri(fileName);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
public void PrintDocumentHandler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
((WebBrowser)sender).Print();
((WebBrowser)sender).Dispose();
Application.ExitThread();
}
Everything runs through fine. However, the WebBrowser object refuses to print. There are no errors (that are obvious), the program finishes off with nothing sent to the printer. When I take away the threading, the program works again.
My knowledge of threading is weak, and I'm pretty much teaching myself - so presumably I'm misunderstanding how threading priority is set.
Here's How it should work:
User selects Invoice(s) on Main Form, chooses to print.
Background thread goes away and prints them while user continues on the program.
Any ideas would be appreciated.
Main problem with your code is WebBrowser wrong using.
WebBrowser supposed to be used for interactive web-browsing, during it user do some things in the internet. But in your case you are using WebBrowser just for the printing after downloading the html. This is wrong by two reasons:
Your code creates whole Windows Forms Control and not using even half of its functionality.
Your code tries to use the WinForms Control in the background thread, which leads to the unexpected behaviour.
BackgroundWorker class supposed to be used for
execute a time-consuming operation (like downloads and database transactions) in the background.
Much more:
You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.
Your code will fail in the background thread, because WinForms control is a user-interface object.
Just for the record, WebBrowser.Print method invokes native windows API, so you have no chance that this will work in background. From the disassembly code:
this.AxIWebBrowser2.ExecWB(NativeMethods.OLECMDID.OLECMDID_PRINT,
NativeMethods.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
ref obj,
IntPtr.Zero);
So, my suggestion for your code is:
Remove usage of the WebBrowser class in the background. Use HttpWebRequest instead for downloading the web content.
Choose other way for the printing your downloaded content. Options are:
PrintDocument implementation (example for it is here).
Use MS Office classes for opening and printing your html-files (first parameter for the Print method is a Boolean Background, I think this can help you). As far as I know, even in 2010 this approach works well.
Check other questions about printing (here is a discussion of printing the images, but this is the same thing, I think).
PS: in the comments you've said that you may need the PDF from your html. I did this by C# by two ways:
Batching using the wkhtmltopdf
Using Microsoft Office Add-in: Microsoft Save as PDF or XPS.
This Add-in should be installed on the server, after that you can easily use MS Office classes for saving the output in the PDF format.
Some update here:
As we have an async/await and TPL options for the time-consuming operations, I don't recommend you to use the BackgroundWorker class anymore.
I'm creating an application that uses .Net and Mono, it uses cross-threaded forms as I was having bad response from the child windows.
I created a test program with 2 forms: the first (form1) has a single button (button1) and the second (form2) is blank, code snippet below.
void openForm()
{
Form2 form2 = new Form2();
form2.ShowDialog();
}
private void button1_Click(object sender, EventArgs e)
{
Thread x = new Thread(openForm);
x.IsBackground = true;
x.Start();
}
This works fine in .Net, but with Mono, the first window will not gain focus when you click it (standard .ShowDialog() behaviour) rather than .Show() behaviour as .Net uses.
When I use .Show(), on .Net and Mono the window just flashes then disappears. If I put a 'MessageBox.Show()' after 'form2.Show()' it will stay open until you click OK.
Am I missing something in that code or does Mono just not support that? (I'm using Mono 2.8.1)
Thanks in advance, Adrian
EDIT: I realised I forgot 'x.IsBackground = true;' in the code above so child windows will close with the main window.
It's almost never the right thing to do in a Windows app to have more than one thread talk to one window or multiple windows which share the same message pump.
And it's rarely necessary to have more than one message pump.
The right way to do this is either to manually marshal everything back from your worker threads to your Window, using the 'Invoke' method, or use something like BackgroundWorker, which hides the details for you.
In summary:
Don't block the UI thread for time-consuming computation or I/O
Don't talk to the UI from more than one thread.
If you use Winforms controls, you shold "touch" the object always in main UI thread.
And at least - calling new Form.ShowDialog() in new thread does not make sense.
EDIT:
If you want easy work with Invoke/BeginInvoke you can use extension methods:
public static class ThreadingExtensions {
public static void SyncWithUI(this Control ctl, Action action) {
ctl.Invoke(action);
}
}
// usage:
void DoSomething( Form2 frm ) {
frm.SyncWithUI(()=>frm.Text = "Loading records ...");
// some time-consuming method
var records = GetDatabaseRecords();
frm.SyncWithUI(()=> {
foreach(var record in records) {
frm.AddRecord(record);
}
});
frm.SyncWithUI(()=>frm.Text = "Loading files ...");
// some other time-consuming method
var files = GetSomeFiles();
frm.SyncWithUI(()=>{
foreach(var file in files) {
frm.AddFile(file);
}
});
frm.SyncWithUI(()=>frm.Text = "Loading is complete.");
}
I'm facing problem in showing FolderBrowserDialog instance created and called from a non-UI thread. It doesn't get renders properly.
Being more specific, it doesn't shows the folder tree but displays only the Make New Folder OK and Cancel
All the shell dialogs, including FolderBrowserDialog, require the COM apartment for the thread to be set to STA. You are probably missing the Thread.SetApartmentState() call:
private void button1_Click(object sender, EventArgs e) {
var t = new Thread(() => new FolderBrowserDialog().ShowDialog());
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
Beware that you cannot set the owner of the dialog, it easily gets lost behind a window of another application. Which makes showing forms or dialogs on a worker thread less than a good idea.
I am not sure why you would want to do this. On a worker-thread all neccessary values for your calculation should be available. There should be no need for user-interaction to get more input.
Maybe a redesign would be more helpful in your case. Think about providing the selected folder to your worker-thread before starting it.
EDIT (reply to the comment):
If you want to do some logging my answer still applies. Your worker-thread should know where to log exceptions and not start to ask the user.
Do you use a logging framework? If not, have a look at log4net for instance. Here you normally pre-configure your logging (the log-level, path, format, ...) in a xml-file. There is no user interaction needed. Though the user could change the logging path (in the xml file).