I'm trying to render some html content to a bitmap in a Windows Service.
I'm using System.Windows.Controls.WebBrowser to perform the render. The basic rendering setup works as a standalone process with a WPF window hosting the control, but as a service, at least I'm not getting the LoadCompleted events to fire.
I know that I at least need a Dispatcher or other message pump looping for this WPF control. Perhaps I'm doing it right and there are just additional tricks/incompatibilities necessary for the WebBrowser control. Here's what I've got:
I believe only one Dispatcher needs to be running and that it can run for the life of the service. I believe the Dispatcher.Run() is the actual loop itself and thus needs it's own thread which it can otherwise block. And that thread needs to be [STAThread] in this scenario. Therefore, in a relevant static constructor, I have the following:
var thread = new Thread(() =>
{
dispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
where dispatcher is a static field. Again, I think there can only be one but I'm not sure if I'm supposed to be able use Dispatcher.CurrentDispatcher() from anywhere instead and get the right reference.
The rendering operation is as follows. I create, navigate, and dispose of the WebBrowser on dispatcher's thread, but event handler assignments and mres.Wait I think may all happen on the render request-handling operation. I had gotten The calling thread cannot access this object because a different thread owns it but now with this setup I don't.
WebBrowser wb = null;
var mres = new ManualResetEventSlim();
try
{
dispatcher.Invoke(() => { wb = new WebBrowser(); });
wb.LoadCompleted += (s, e) =>
{
// Not firing
};
try
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.Unicode))
{
sw.Write(html);
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
// GO!
dispatcher.Invoke(() =>
{
try
{
wb.NavigateToStream(ms);
Debug.Assert(Dispatcher.FromThread(Thread.CurrentThread) != null);
}
catch (Exception ex)
{
// log
}
});
if (!mres.Wait(15 * 1000)) throw new TimeoutException();
}
}
catch (Exception ex)
{
// log
}
}
finally
{
dispatcher.Invoke(() => { if (wb != null) wb.Dispose(); });
}
When I run this, I get my timeout exception every time since the LoadCompleted never fires. I've tried to verify that the dispatcher is running and pumping properly. Not sure how to do that, but I hooked a few of the dispatcher's events from the static constructor and I get some printouts from that, so I think it's working.
The code does get to a wb.NavigateToStream(ms); breakpoint.
Is this bad application of Dispatcher? Is the non-firing of wb.LoadCompleted due to something else?
Thanks!
Here's a modified version of your code which works as a console app. A few points:
You need a parent window for WPF WebBrowser. It may be a hidden window like below, but it has to be physically created (i.e. have a live HWND handle). Otherwise, WB never finishes loading the document (wb.Document.readyState == "interactive"), and LoadCompleted never gets fired. I was not aware of such behavior and it is different from the WinForms version of WebBrowser control. May I ask why you picked WPF for this kind of project?
You do need to add the wb.LoadCompleted event handler on the same thread the WB control was created (the dispatcher's thread here). Internally, WPF WebBrowser is just a wrapper around apartment-threaded WebBrowser ActiveX control, which exposes its events via IConnectionPointContainer interface. The rule is, all calls to an apartment-threaded COM object must be made on (or proxied to) the thread the object was originally created on, because that's what such kind of objects expect. In that sense, IConnectionPointContainer methods are no different to other methods of WB.
A minor one, StreamWriter automatically closes the stream it's initialized with (unless explicitly told to not do so in the constructor), so there is no need to for wrapping the stream with using.
The code is ready to compile and run (it requires some extra assembly references: PresentationFramework, WindowsBase, System.Windows, System.Windows.Forms, Microsoft.mshtml).
using System;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.IO;
using System.Runtime.InteropServices;
using mshtml;
namespace ConsoleWpfApp
{
class Program
{
static Dispatcher dispatcher = null;
static ManualResetEventSlim dispatcherReady = new ManualResetEventSlim();
static void StartUIThread()
{
var thread = new Thread(() =>
{
Debug.Print("UI Thread: {0}", Thread.CurrentThread.ManagedThreadId);
try
{
dispatcher = Dispatcher.CurrentDispatcher;
dispatcherReady.Set();
Dispatcher.Run();
}
catch (Exception ex)
{
Debug.Print("UI Thread exception: {0}", ex.ToString());
}
Debug.Print("UI Thread exits");
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static void DoWork()
{
Debug.Print("Worker Thread: {0}", Thread.CurrentThread.ManagedThreadId);
dispatcherReady.Wait(); // wait for the UI tread to initialize
var mres = new ManualResetEventSlim();
WebBrowser wb = null;
Window window = null;
try
{
var ms = new MemoryStream();
using (var sw = new StreamWriter(ms, Encoding.Unicode)) // StreamWriter automatically closes the steam
{
sw.Write("<b>Hello, World!</b>");
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
// GO!
dispatcher.Invoke(() => // could do InvokeAsync here as then we wait anyway
{
Debug.Print("Invoke Thread: {0}", Thread.CurrentThread.ManagedThreadId);
// create a hidden window with WB
window = new Window()
{
Width = 0,
Height = 0,
Visibility = System.Windows.Visibility.Hidden,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Content = wb = new WebBrowser();
window.Show();
// navigate
wb.LoadCompleted += (s, e) =>
{
Debug.Print("wb.LoadCompleted fired;");
mres.Set(); // singal to the Worker thread
};
wb.NavigateToStream(ms);
});
// wait for LoadCompleted
if (!mres.Wait(5 * 1000))
throw new TimeoutException();
dispatcher.Invoke(() =>
{
// Show the HTML
Console.WriteLine(((HTMLDocument)wb.Document).documentElement.outerHTML);
});
}
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
finally
{
dispatcher.Invoke(() =>
{
if (window != null)
window.Close();
if (wb != null)
wb.Dispose();
});
}
}
static void Main(string[] args)
{
StartUIThread();
DoWork();
dispatcher.InvokeShutdown(); // shutdown UI thread
Console.WriteLine("Work done, hit enter to exit");
Console.ReadLine();
}
}
}
Maybe the Webbrowser Control needs Desktop Interaction for rendering the content:
My feeling say that using WPF controls and in particular particulary the Webbrowser-Control (=Wrapper around the IE ActiveX control) isn't the best idea.. There are other rendering engines that might be better suited for this task: Use chrome as browser in C#?
Related
I have app with 2 windows.
1st LoginWindow used to authentificate user and launch main app. I use thread and run dispatcher for that:
private bool EndTrigger = false;
/.../
Thread thread = new Thread(() =>
{
MainWindow T_window = new MainWindow(t_data);
T_window.WindowState = WindowState.Maximized;
T_window.Show();
EndTrigger = true;
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
After that LoginWindow is closed. I used function that checks if MainWindow is ready and Timer like this:
Timer LoginWinClose = new Timer(new TimerCallback(IfLoginWinCanBeClosed), null, 2000, 1000);
and
public void IfLoginWinCanBeClosed(Object stateInfo)
{
if (EndTrigger)
{
this.Dispatcher.Invoke(new Action(delegate
{
this.Close();
}));
}
}
It works as it should: LoginWindow disapper, MainWidow appear and everything works.
But when I tryed to create one more window in MainWindow I get Exception that tells me: Application is shutting down.
It looks like closing LoginWindow leads to attemp of closing application, but if I close any other window (for example MainWindow), I still can create one more from LoginWondow without any error.
Currently I solve this by by changing
this.Close();
to
this.Visibility = Visibility.Collapsed;
It means that LoginWindow will continue to run all the time. If there any another solution?
Thanks to #Sham I understand where is the mistake!
Code, where new window is created located in separate thread (this is because login check operations run in the separated thread to avoid hanging LoginWindow), so I Create and run new window with separate dispatcher in that Thread, instead of main UI thread.
So, the solution is quite easy. Just need to make a little modification:
Thread thread = new Thread(() =>
{
this.Dispatcher.Invoke(new Action(delegate
{
AdminWindow T_window = new AdminWindow(t_data);
T_window.WindowState = WindowState.Maximized;
T_window.Show();
t_data.Link_auth_win.EndTrigger = true;
System.Windows.Threading.Dispatcher.Run();
}));
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
I'm coding a Unit testing and for debug purposes I do need to see some data (please do not discus if one should be using UI for unit testing)
I created a UI thread and started my component like this:
System.Threading.Thread newWindowThread = new Thread(new ThreadStart(() =>
{
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
plotter = new PlotterWPF();
plotter.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
plotter.Show();
plotter.init();
System.Windows.Threading.Dispatcher.Run();
}));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.Start();
This works and the plotter appears in screen, my proble is afterwards when I want to send stuff to the plotter I do this:
public void debugFrame(Gtec2.Frame frame)
{
plotter.Dispatcher.Invoke(() =>
{
plotter.Plot(frame);
});
}
and it fails because the plotter is null (in the plotter.Dispatcher... part)
I tried with Applicaiton.Current.Dispatcher but .. application.Current is null.
I also tried to create the plotter outside the thread but .. I cannot because is not STA. I think there should be a way to let the thing now my Dispatcher is the newWindowThread and the dispatcher.Invoke should happen there but .. I have no clue how to do it ..
Any suggestions?
dispatcher = System.Windows.Threading.Dispatcher.FromThread(newWindowThread);
This grabs the dispatcher for this particular thread, from there one one can do
dispatcher.Invoke(() =>
{
/*here you can access the plotter */
});
I know normally one is not supposed to touch UI elements from threads other than the UI thread, but I am new to WPF and I am wondering if my current working implementation can be improved.
I have an application that is comprised solely of a notification tray icon, and I want to update that icon from a background thread.
Here is my Program.cs entry point:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (IconHandler notify = new IconHandler())
{
notify.Display();
Application.Run();
}
}
}
This is my IconHandler.cs notification icon handler class:
class IconHandler : IDisposable
{
NotifyIcon ni;
public IconHandler()
{
ni = new NotifyIcon();
}
public void Display()
{
ni.MouseClick += new MouseEventHandler(ni_MouseClick);
ni.Icon = Resources.icon1;
ni.Visible = true;
new Thread(new ThreadStart(UpdateIcon)).Start();
}
public void UpdateIcon()
{
while (true)
{
// reference ni directly, it updates fine
}
}
public void Dispose()
{
ni.Dispose();
}
void ni_MouseClick(object sender, MouseEventArgs e)
{
// something useful
}
}
Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?
Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?
To begin with NotifyIcon is not a WPF control, but comes from the Windows Forms namespace. As such it has normal C# properties (e.g. Icon, Visible) meaning you have been able to alter the icon property in the non-UI thread without an exception being raised. If you had used a WPF controls then they have Dependency Properties and direct manipulation of Dependency Properties outside of the UI thread will cause an exception to be raised.
Will I run into lifecycle issues with this setup?
You've currently NOT created a WPF window or WPF controls. If your application develops such that you start using WPF and the UpdateIcon method is expanded to do more than you currently do and access these WPF objects then yes you will need a strategy to deal with the updates from non-UI threads.
You can hide some of this cross-threaded access using some helper methods.
Example 1 If your strategy becomes referencing WPF controls programmatically from the background thread then you can use a helper method such as this.
It first checks if the call is on the UI thread, if so then it updates the control directly, otherwise it will schedule that the method (itself) be called from the UI thread at a later point in time.
I've used BeginInvoke here so that the background thread can continue before the UI thread has actually called the method. If you want to block the background thread then use Invoke instead.
public void UpdateLabel(Label control, string text)
{
if (Application.Current.Dispatcher.CheckAccess())
control.Content = text;
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => UpdateLabel(control, text)), DispatcherPriority.Normal);
}
Example 2
If your strategy uses Events raised on the background thread to update the WPF controls programmatically then you can hide some of the cross-threading calls as part of raising the event, leaving the WPF update routine quite clean and simple to read.
Any event handlers of this event can be coded knowing that the call will be made from the UI thread, so no threading issues.
public void OnRaiseEvent(EventHandler handler, EventArgs args)
{
if (handler != null)
{
if (Application.Current.Dispatcher.CheckAccess())
handler(sender, new PropertyChangedEventArgs(propName));
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, args)), DispatcherPriority.Normal);
}
}
Example 3
If your future strategy fully utilizes the benefits of WPF with Binding (as opposed to programmatically updating your WPF controls), then you can embed the cross-threading code into the data-bound objects.
If for example your XAML databinds to the MyProperty property of an instance of the MyDataClass class and that class implements the INotifyPropertyChanged interface you can put the cross-threading code in the data class making it possible to update the data from any thread. Here is the example of the class:-
public class MyDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _myProperty;
public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value); } }
}
This class utilizes the SetValueAndNotify extension method on the PropertyChanged event. It is in here we hide the cross-threading code to simplify other parts of the code. Here's the definition of this extension method.
public static class PropertyChangedExtension
{
public static void SetValueAndNotify<T>(this PropertyChangedEventHandler handler, object sender, ref T destination, T source, [CallerMemberName] string propName = "notset")
{
// Is the new value different from the previous value? If there is no difference then there is nothing more to do
if (Equals(destination, source))
return;
// If we got to this point then the new value is different from the old value, so lets make the assignemnt and raise the property changed event
destination = source;
if (handler != null)
{
if (Application.Current.Dispatcher.CheckAccess())
handler(sender, new PropertyChangedEventArgs(propName));
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, new PropertyChangedEventArgs(propName))), DispatcherPriority.Normal);
}
}
}
The above example uses the [CallerMemberName] attribute from C#5 to remove any typing errors in supplying the property name for the INotifyPropertyChanged arguments. If you are not using the latest then you will need to modify the getter and setter as follows:-
public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value, "MyProperty"); } }
You must always update UI from UI thread only, however, you can schedule some work on UI thread from background thread using dispatcher
public void Display()
{
ni.MouseClick += new MouseEventHandler(ni_MouseClick);
ni.Icon = Resources.icon1;
ni.Visible = true;
new Thread(new ThreadStart(UpdateIcon)).Start();
}
public void UpdateIcon()
{
while (true)
{
//do some long running work
Application.Current.Dispatcher.Invoke(()=>{
//update ui
});
}
}
But if you don't have long running work and you just want to do something periodically, you should use DispatcherTimer instead of loop in background thread.
The while(true) loop in your code will cause heavy CPU/resource usage. maybe add e.g. Thread.Sleep(1000) into the loop to allow for a break between updates.
The best usage of background threads is to perform the long-running work (e.g. communication with server/DB) on the background thread and once the thread completes, have the UI thread update the UI.
With BackgroundWorker:
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
// long running work
};
worker.RunWorkerCompleted += (sender, args) =>
{
// Update UI
};
worker.RunWorkerAsync();
async/await pattern:
public async void DoWork()
{
// Do long running task
var data = await Task.Run(() => new object());
// Update UI here
}
TaskFactory:
Task.Factory.StartNew(() => new Object()).ContinueWith(task => MessageBox.Show(task.Result.ToString()), TaskScheduler.FromCurrentSynchronizationContext());
If the UI needs to update on a constant loop, maybe use a timer to restart the process on a regular basis. This will save your CPU from taking a pounding.
In my application (.NET 4.0) I use smartassembly for error reporting with a custom template. It installs two handlers:
It installs a global exception catcher, and calls my custom code if an exception occurs. There I display a WPF window which shows the details of the exception and allows the user to send the data via the internet.
If an exception occurs which cannot be handled by #1, it calls a fatal exception handler. There I output the exception data in a message box.
On one customer's machine (Windows XP, .NET 4.0) he gets an error message from #2 after the application starts. Then the application is terminated:
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
at System.Windows.Threading.Dispatcher.VerifyAccess()
at Exapt.ErrorReporting.ErrorReportView..ctor()
at Exapt.ErrorReporting.ExaptUnhandledExceptionHandler.OnReportException(ReportExceptionEventArgs e)
at SmartAssembly.SmartExceptionsCore.UnhandledExceptionHandler.ReportException(Exception exception, Boolean canContinue, Boolean manuallyReported)
The relevant code:
public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
{
protected override void OnReportException(ReportExceptionEventArgs e)
{
var view = new ErrorReportView();
view.DataContext = new ErrorReportViewModel(this, e, view);
view.ShowDialog();
}
}
public ErrorReportView : Window
{
public ErrorReportView()
{
this.InitializeComponent();
// EDIT
if (Application.Current != null)
this.Owner = Application.Current.MainWindow;
// END EDIT
}
}
So the following happens:
During startup an exception occurs (unfortunately, this gets lost).
To handle the exception, smartassembly calls the handler #1, OnReportException().
There I create a new ErrorReportView.
WPF throws a cross-thread exception in the constructor (before InitializeComponent())!
Because an exception occurred while handling the exception, smartassembly calls the handler #2 and terminates the application.
How is it possible that a simple new Window() can cause a cross-thread exception on itself?
Try to create your ErrorReportView using the WPF Dispatcher :
public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
{
protected override void OnReportException(ReportExceptionEventArgs e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
var view = new ErrorReportView();
view.DataContext = new ErrorReportViewModel(this, e, view);
view.ShowDialog();
}));
}
}
As I can't test it or reproduce your issue, I'm not sure it will work, but it's worth a try.
An option is to fire a dedicated thread to handle this report. It would be something like this:
[TestMethod]
public void TestMethod1()
{
MainWindow window = null;
// The dispatcher thread
var t = new Thread(() =>
{
window = new MainWindow();
// Initiates the dispatcher thread shutdown when the window closes
window.Closed += (s, e) => window.Dispatcher.InvokeShutdown();
window.Show();
// Makes the thread support message pumping
System.Windows.Threading.Dispatcher.Run();
});
// Configure the thread
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
}
Note that:
The window must be created and shown inside the new thread.
You must initiate a dispatcher (System.Windows.Threading.Dispatcher.Run()) before the ThreadStart returns, otherwise the window will show and die soon after.
The thread must be configured to run in STA apartment.
You can find additional information in this link.
With Arthur Nunes's answer and Sisyphe's answer I now handle all possibilities. The exception is apparently being thrown on an STA thread, but that thread is not the main (UI) thread. Probably due to JIT optimizations, the stack trace I got is a little incomplete and shows the exception happening at the wrong place.
The fixed code:
public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
{
protected override void OnReportException(ReportExceptionEventArgs e)
{
// Create a new STA thread if the current thread is not STA.
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
this.ShowErrorReportView(e);
}
else
{
// Since I use ShowDialog() below, there is no need for Dispatcher.Run()
// or Dispatcher.InvokeShutdown()
var thread = new Thread(() => this.ShowErrorReportView(e));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
}
private void ShowErrorReportView(ReportExceptionEventArgs e)
{
var view = new ErrorReportView();
view.DataContext = new ErrorReportViewModel(this, e, view);
view.ShowDialog();
}
}
public ErrorReportView : Window
{
public ErrorReportView()
{
this.InitializeComponent();
// All of these cause accessing the MainWindow property or setting the Owner
// to throw an exception.
if (Application.Current != null
&& Application.Current.Dispatcher.CheckAccess()
&& Application.Current.MainWindow != null
&& Application.Current.MainWindow != this
&& Application.Current.MainWindow.IsLoaded)
{
this.Owner = Application.Current.MainWindow;
}
}
}
In my wpf application, I have a menu. When I click on one of the elements of the menu, I change my screen data, which is quite a long process.
I tried to disable the main window when I do such a loading, using this method :
private void SetNavigation(MainContentTypeEnum enumVal, int id, ICheckState vm)
{
var parent = Window.GetWindow(this);
var tmpCursor = parent.Cursor;
parent.Cursor = Cursors.Wait;
parent.IsEnabled = false;
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += (o, args) =>
{
try
{
Dispatcher d = args.Argument as Dispatcher;
d.Invoke(new Action(() =>
{
Navigation.Navigator.SetContol(enumVal, id, vm);
}));
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
};
bw.RunWorkerCompleted += (o, args) =>
{
parent.IsEnabled = true;
parent.Cursor = tmpCursor;
};
bw.RunWorkerAsync(Dispatcher.CurrentDispatcher);
}
This method works on the very first call, the form is disabled, and then enabled when data is loaded. But on next calls, it doesn't work anymore, everything freezes until the operation completes. I tried setting a breakpoint, and the method is correctly hit and executed. I don't understant why it only works one time...
Have you an idea ?
Thanks in advance!
Edit: A bit of precision: this code is part of a usercontrol, which is why I call the parent using Window.GetWindow(this);
Edit2: Setting a Thread.Sleep(1000); just before invoking the dispatcher does the job. My guess is that the parent.IsEnabled instruction is not executed quickly enough... but why ?
Edit3: Having made some timings, my data retrieval is quite quick. It seems that the problem exists on the binding phase. I set the value to the bound property, and the method returns. However, the UI still frozen for a moment after that.