|
Mark Salsbery wrote: fjparisIII wrote:
I would actually be shocked with disbelief if the BackgroundWorker thread doesn't automatically run with ApartmentState.STA
I'm not sure about that....I'll look it up after lunch...
I didn't know where to look it up so I just tested for what it was. It is MTA. Do you know why this would prevent things like BitmapEncoder.Save() from executing correctly in an MTA thread?
|
|
|
|
|
fjparisIII wrote: Do you know why this would prevent things like BitmapEncoder.Save() from executing correctly in an MTA thread?
According to replies in those links - COM.
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
Mark Salsbery wrote: And your kludge sounds horrific - I would dump that
I can't dump it until I refactor my code to replace my usage of the BackgroundWorker class with my own custom thread that does exactly the same thing only is STA instead of MTA: massive re-invention of the wheel. I was only sucked into using BackgroundWorker in the first place because it was recommended as an easy way to maintain a reactive UI while a background task was grinding away doing the real work.
If the reason all these people are having trouble with BitmapEncoder.Save() is because they are trying to execute it inside BackgroundWorker and BitmapEncoder.Save() can only be executed in an STA, why did the designers of BackgroundWorker make it an MTA? It makes BackgroundWorker useless for transforming bitmaps in the background, as long as WPF uses WIC (COM-based) to implement the BitmapEncoder classes.
How about warning us up front that we won't be able to transform bitmaps inside BackgroundWorker and that we'll have to roll our own threads with the exact same functionality provided by the BackgroundWorker events, ProgressChanged and RunWorkerCompleted?
It makes me suspicious about the probability of success in going through the effort of this refactoring. What if there's a good reason for running BackgroundWorker as MTA and if my own STA replacement won't work for these very same reasons? (Can you think of any?) Then I'm still out in the cold, stuck with my "horrific kludge," as you called it. In the meantime I've wasted several days going down a blind alley.
|
|
|
|
|
fjparisIII wrote: massive re-invention of the wheel
Maybe a bit, but not massive. BackgroundWorker is a convenience class.
Creating a System.Thread and having to invoke updates on the UI thread
just means a few more lines of code, but it's pretty simple. Of course, wrap
it in a class so you only have to do it once.
When a convenience class doesn't do what you need, sometimes you just have
to go lower level.....in my experience, that's been a recurring theme for
quite a long time
As for the rest - this should be reported to Microsoft through the Connect site.
At the very minimum, I'd say the documentation needs to be fixed/expanded.
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
I took your advice and posted a suggestion on the Connect site: https://connect.microsoft.com/WPF/feedback/ViewFeedback.aspx?FeedbackID=462849[^]
In the meantime I'm struggling with the prospect of writing a replacement class for BackgroundWorker, struggling because I've never written any threading code under .NET. I've been reading various books I have about .NET and WPF the last few hours and not finding the answers I need. One very pressing problem in my mind is, if I create events equivalent to ProgressChanged and RunWorkerCompleted, and they are trigged within my own thread class, how do I guarantee that the event handlers get executed in the WPF UI thread and not the worker thread that I create? Is this as simple as executing the code to create the event handlers within the UI thread instead of within the worker thread?
Asking a simple question like this indicates how green I am at doing this sort of thing and why I am so intimidated by the prospect. I'm worried I'll go through this big learning curve, carefully constructing my own BackgroundWorker class, only to find out that the solution doesn't work anyhow.
|
|
|
|
|
fjparisIII wrote: In the meantime I'm struggling with the prospect of writing a replacement class for BackgroundWorker, struggling because I've never written any threading code under .NET.
Just for fun I tried this....
public class MyWPFBackgroundWorker
{
private delegate void InvokerDelegate();
public event DoWorkEventHandler DoWork;
public event ProgressChangedEventHandler ProgressChanged;
public event RunWorkerCompletedEventHandler RunWorkerCompleted;
public void RunWorkerAsync()
{
RunWorkerAsync(null);
}
public void RunWorkerAsync(Object argument)
{
threadargument = argument;
thread = new Thread(ThreadProc);
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Normal;
thread.Start(this);
}
public void ReportProgress(int percentProgress)
{
ReportProgress(percentProgress, null);
}
public void ReportProgress(int percentProgress, Object userState)
{
ProgressChangedEventHandler progresshandler = ProgressChanged;
if (progresshandler != null)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (InvokerDelegate)delegate
{
progresshandler(this, new ProgressChangedEventArgs(percentProgress, userState));
});
}
}
Thread thread;
Object threadargument;
static void ThreadProc(object context)
{
MyWPFBackgroundWorker worker = context as MyWPFBackgroundWorker;
DoWorkEventHandler workhandler = worker.DoWork;
if (workhandler != null)
{
workhandler(worker, new DoWorkEventArgs(worker.threadargument));
}
RunWorkerCompletedEventHandler completedhandler = worker.RunWorkerCompleted;
if (completedhandler != null)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (InvokerDelegate)delegate
{
completedhandler(worker, new RunWorkerCompletedEventArgs(null, null, false));
});
}
}
}
Seems to work like BackgroundWorker, except it's not on a threadpool thread and the
thread state is STA...please let me know about any bugs
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
|
fjparisIII wrote: BackgroundWorker has 17 methods, 9 properties, and 4 events.
How many are inherited? I didn't derive from Component.
I didn't implement cancel ability...I'll add that next.
I plugged the class into an existing WPF app, replacing "BackgroundWorker" with
"MyWPFBackgroundWorker" and it worked fine.
But I just use the basic BackgroundWorker stuff - the 3 main events, and the
RunWorkerAsync() method(s).
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
|
fjparisIII wrote: there's probably just some feature you're using that I'm not aware of.
You're not missing anything.
I tried to demonstrate a simple BackgroundWorker-like class (I even used the same
event delegates, although I could have implemented my own) that runs the secondary
thread in STA state, that's it.
If you want/need to derive from BackgroundWorker and rewrite it all, go ahead.
If you want/need to implement every last bit of BackgroundWorker than feel free.
I implemented the features I commonly use.
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
|
fjparisIII wrote: If you've got some canned code I can just pick up and shove into my app
that's what I tried to provide.
If you take the latest version I posted (below, the class called "WPFSTABackgroundWorker"),
add it to an app that uses BackgroundWorker, and replace any occurrence of "BackgroundWorker"
in your existing code with "WPFSTABackgroundWorker", it should work, in STA mode, unless you've
used some feature of BackgroundWorker that I didn't implement, which would be the Component class
stuff.
As for what I did -
I focused on the key convenience features of BackgroundWorker:
1) Handles starting the thread, providing a simple plug-in event handler for users
to provide the thread procedure code.
2) Provides progress update and thread end events on the UI thread.
3) Provides the ability to asynchronously cancel (in my second version)
My functional requirements:
1) I wanted it to replace BackgroundWorker and be source compatible with
BackgroundWorker, except for the class name.
2) I don't use any of the Component class features, so I saw no need to implement
that class like BackgroundWorker does.
A couple things that make my implementation not as good as BackgroundWorker
(I'm sure many more could be found):
1) Doesn't implement Component
2) I used the WPF Application class to raise events on the UI thread, so the
class is only usable in a WPF app.
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
|
If an STA thread doesn't fix it:
Please post the exact exception message. Maybe the code that creates the
stream and calls Save() too (if the exception is still happening there)...
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
Cancellation and other non-Component properties implemented...
public class WPFSTABackgroundWorker
{
private delegate void InvokerDelegate();
public event DoWorkEventHandler DoWork;
public event ProgressChangedEventHandler ProgressChanged;
public event RunWorkerCompletedEventHandler RunWorkerCompleted;
public bool WorkerReportsProgress { get; set; }
public bool IsBusy { get; protected set; }
public bool WorkerSupportsCancellation { get; set; }
private bool _cancellationPending;
public bool CancellationPending
{
get
{
bool ret;
lock (cancellock)
{
ret = _cancellationPending;
}
return ret;
}
protected set
{
lock (cancellock)
{
_cancellationPending = value;
}
}
}
public WPFSTABackgroundWorker()
{
IsBusy = false;
WorkerReportsProgress = false;
WorkerSupportsCancellation = false;
CancellationPending = false;
}
public void RunWorkerAsync()
{
RunWorkerAsync(null);
}
public void RunWorkerAsync(Object argument)
{
IsBusy = true;
threadargument = argument;
thread = new Thread(ThreadProc);
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Normal;
thread.Start(this);
}
public void CancelAsync()
{
if (!WorkerSupportsCancellation)
throw new InvalidOperationException("Attempted to CancelAsync when WorkerSupportsCancellation is false.");
CancellationPending = true;
}
public void ReportProgress(int percentProgress)
{
ReportProgress(percentProgress, null);
}
public void ReportProgress(int percentProgress, Object userState)
{
if (!WorkerReportsProgress)
throw new InvalidOperationException("Attempted to ReportProgress when WorkerReportsProgress is false.");
ProgressChangedEventHandler progresshandler = ProgressChanged;
if (progresshandler != null)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (InvokerDelegate)delegate
{
progresshandler(this, new ProgressChangedEventArgs(percentProgress, userState));
});
}
}
Thread thread;
Object threadargument;
static void ThreadProc(object context)
{
WPFSTABackgroundWorker worker = context as WPFSTABackgroundWorker;
DoWorkEventHandler workhandler = worker.DoWork;
if (workhandler != null)
{
workhandler(worker, new DoWorkEventArgs(worker.threadargument));
}
RunWorkerCompletedEventHandler completedhandler = worker.RunWorkerCompleted;
if (completedhandler != null)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (InvokerDelegate)delegate
{
completedhandler(worker, new RunWorkerCompletedEventArgs(null, null, worker.CancellationPending));
});
}
worker.IsBusy = false;
}
}
Mark Salsbery
Microsoft MVP - Visual C++
modified on Monday, June 1, 2009 8:23 PM
|
|
|
|
|
|
fjparisIII wrote: when I copy and paste your code into mine, it all comes out in one line. Do you know how I can get a copy of your code that is properly formatted?
Good question! I use Firefox and I haven't seen that happen.
Here's a copy of the file I am testing with[^]
Hope that works better!
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
|
|
|
Okay, I subscribed to the three events in C# code and ran the app and when it hit the BitmapEncoder.Save() call, my message box produced the following output:
It would probably fail on all images, but the first one it failed on is the following:
C:\temp\ImageScaling\SelectedImages\jpg\001.jpg
Reason: The image data generated an overflow during processing.
This is what I have also been getting with all versions of my code that I have tested with, including my own reimplementation of BackgroundWorker that I tested with yesterday. This time I took a breakpoint where I output this message so I could recover some details about the exception that my message box does not display:
HResult: -2146233066
innerException: {"Overflow or underflow in the arithmetic operation."}
message: The image data generated an overflow during processing.
So the overflow the Exception.Message property is talking about is an arithmetic overflow. So BitmapEncoder.Save() is probably getting an empty buffer or something with all zeroes in it and when it tries to encode the JPEG image data it generates an arithmetic overflow. So the real question is, why doesn't BitmapEncoder.Save() have the actual image data to work with?
I think this conclusively proves that whether BackgroundWorker runs as STA or MTA has nothing to do with my problem, what I expected all along.
modified on Sunday, June 7, 2009 1:15 PM
|
|
|
|
|
fjparisIII wrote: I think this conclusively proves that whether BackgroundWorker runs as STA or MTA has nothing to do with my problem
Right, but your very first post had this:
"The code that I have developed based on the above thread works 100%
of the time if it is executed inside of the WPF main application thread.
But it fails 100% of the time if it is executed inside of a worker thread.
The code fails on BitmapEncoder.Save(), throwing a "Cannot write to the
stream" exception. But I have to execute this code inside of a worker
thread because I have to display a progress bar: I'm updating the Copyright
metadata in a batch operation for thousands of images!"
That's what I was addressing...
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
There are a couple of posts you made I have to respond to. I'll do this one first because it's the easiest.
You were responding to the following quote from a previous post of mine:
Mark Salsbery wrote: your very first post had this:
"The code that I have developed based on the above thread works 100%
of the time if it is executed inside of the WPF main application thread.
But it fails 100% of the time if it is executed inside of a worker thread.
The code fails on BitmapEncoder.Save(), throwing a "Cannot write to the
stream" exception. But I have to execute this code inside of a worker
thread because I have to display a progress bar: I'm updating the Copyright
metadata in a batch operation for thousands of images!"
That's what I was addressing...
There are actually three places in my application where I call BitmapEncoder.Save() to capture image metadata that I need to add to an encoded image. One was already running in the main UI thread and was never a problem. The one I'm talking about in the above quote attempts to write "friendly" metadata tags in a batch operation with potentially thousands of files (e.g. the copyright notice). I solved that one with the "kludge" I talked about earlier, of pulling that small piece of code out of the background thread and executing it in the UI thread.
The one I'm still having problems with is a batch operation that scales images, where I have to transfer all of the metadata from the original image to the scaled image.
modified on Sunday, June 7, 2009 1:16 PM
|
|
|
|
|
fjparisIII wrote: So the real question is, why doesn't BitmapEncoder.Save() have the actual image data to work with?
That IS the real question
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
fjparisIII wrote: the next step of passing some source code to you via email and giving you my running application (which you'll be able to get from the Office Live site)
msalsbery at hotmail dot com
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|