|
Try Source="images/add.png"
|
|
|
|
|
Yeah : NO BACKSLASHES IN WPF/Silverlight URIs!
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
I have developed code based on the following post that has been previously reference in this forum: http://blog.andreweichacker.com/2009/02/reading-and-writing-tags-for-photos-in-wpf/comment-page-1/#comment-1492[^]
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!
On a wild guess I thought that BitmapEncoder.Save() might be failing because it is derived from DispatcherObject. But when I call BitmapEncoder.CheckAccess() it returns true, which actually isn't surprising since the BitmapEncoder object is constructed inside the worker thread. So I don't see any reason why I can't execute BitmapEncoder.Save() in the worker thread, but when I try, the BitmapEncoder.Save() throws the exception.
This has been tormenting me for weeks and only today did I realize that what was different about the cases where my class worked and where it didn't is whether BitmapEncoder is instantiated inside a worker thread, and this restriction seems completely irrational to me. So my question is, what am I missing and how can I correct the problem? This problem is holding up a ton of work that I have to do.
modified on Saturday, May 30, 2009 3:03 PM
|
|
|
|
|
I still have no idea why BitmapEncoder.Save() fails in a worker thread. I needed to move on, so I've just given up on that problem and took an "end around." I would still appreciate it if someone could explain how to attack the problem head on.
What I did was create a result code in the worker thread that indicates that BitmapEncoder.Save() needs to be called and that is bound to fail in a worker thread and so the worker thread doesn't even try. Instead it tells the UI thread, "I can't do it, you do it," which instructs the UI thread to call that function from itself. It also sets a flag that the worker thread can test to see if the UI thread is busy doing this nonsense and loops on a Thread.Sleep(0) until the flag is reset. Otherwise, the worker thread can get ahead of the UI thread and cause an access violation. The Sleep causes the progress bar to be a slightly jerky, so it would still be nice to eliminate the root cause of this problem and let me call BitmapEncoder.Save() where it should be called, in the worker thread.
This is such a kludge that it is difficult to document it and I wish I didn't have to do it. But I've been hung up on this problem for about three weeks and I'm just sick of it. In fact, I'm ecstatic that I've found any solution at all that lets me run a worker thread so I can have a reactive progress bar that can be interrupted instantly with the click of a Cancel button.
It's amazing that thousand page books can be written on WPF that barely scratch the surface.
|
|
|
|
|
|
Hi, Mark. Thanks for your reply.
I'm using the the BackgroundWorker class to run my worker thread. In looking through MSDN, I don't see any way of getting the thread object of the background worker before it starts, and I'd need to do that before I could call Thread.SetApartmentState(ApartmentState.STA). I would actually be shocked with disbelief if the BackgroundWorker thread doesn't automatically run with ApartmentState.STA. The only other option is ApartmentState.MTA, and I don't believe that's even supported under WPF.
Also, you think my current kludge is bad! It turns out it doesn't work for my image scaling class. I still get "The image data generated an overflow during processing." So I'm thinking of capturing all of the common metadata items from the original image and adding them one at a time into the resized images after supplying the necessary padding. I'm that desperate. I don't even know if this will work. All I know is that simply copying the entire BitmapMetadata object from the original image with BitmapFrame.Create() does not.
|
|
|
|
|
fjparisIII wrote: I'm using the the BackgroundWorker class to run my worker thread. In looking through MSDN, I don't see any way of getting the thread object of the background worker before it starts
Right - you'd need to use a regular Thread object.
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 am sure curious if there is a threading problem with the encoder classes -
possibly COM related if those links are to be believed...
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
Mark Salsbery wrote: I am sure curious if there is a threading problem with the encoder classes -
possibly COM related if those links are to be believed...
I'm fairly sure the problem is COM-related. All these WPF imaging functions use the WIC API underneath, and those are wickedly ugly COM functions, anathema to C# developers. When programmers signed up to do .NET programming, COM is precisely the sort of garbage they were trying to get away from. I hope and pray Microsoft hasn't abandoned its original promise to rid the world forever of that hopeless ugliness. I'm also amazed at how much P/Invoke programming I have to do to accomplish common tasks. How unconscionable is it that WPF doesn't even offer a folder browser?
The fundamental design of WPF is amazingly elegant. But the version numbers of .NET are hopeless pretentious. I ROFLMAO every time I see them. The released version numbers should have been 0.1, 0.2, 0.3, and 0.35 instead of ten times those values. Well, when you have 100,000,000 lines of code to get out, something has to give, and it would be political suicide to be more honest about what the version numbers imply. At least the basic design is uncompromising, just incomplete. WPF is fun programming, except every time I turn around I'm running into a brick wall and I have to roll up my sleeves and crack open my Win32 background for some P/Invoke programming. And I sure don't want to dive into raw WIC programming. I'll wait for .NET 4.0 and hope for the best.
|
|
|
|
|
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++
|
|
|
|
|