0

I'm currently working on a .NET Desktop application that will be loading potentially large images from disk. Using Image.FromFile works fine, but because these images are potentially quite large I'd like to support some form of progress reporting and cancellation (cancellation being more important to me than progress reporting).

The image needs to be loaded into memory and won't be immediately shown in a UI control, so using something like the PictureBox.LoadAsync/CancelAsync doesn't seem like it's an option (though if there's a way to use a PictureBox to load an image without having it actually present on a parent form/control then I'm open to that).

I'm aware that I can pretty easily read the bytes of the image file from disk with good progress/cancellation support, but the act of getting into an Image (via Image.FromStream in that case) is still a fairly long-running process for larger images.

I'm also aware that I could achieve cancellation via Thread.Abort(), but I'm trying to avoid that approach if possible.

How can I accomplish in .NET?

Jesse Taber
  • 2,376
  • 3
  • 23
  • 33
  • Approach with reading an image as byte stream looks viable here – Pavel Anikhouski Apr 08 '20 at 14:34
  • Since `Image` seams to not expose any kind of async interface I would (like Pavel) that you use the `Image.FromStream` methode to at least load the data async into memory – Ackdari Apr 08 '20 at 14:38
  • 1
    This question has been edited, and the parts that made it off-topic initially have been removed. So I am voting to be reopened. – Theodor Zoulias Apr 08 '20 at 16:30
  • @TheodorZoulias I voted to reopen and will likely edit/delete my answer given the edit. That said, it seems like the ideal answer here is "some method in .NET that's actually async and if one doesn't exist, it should." – Zer0 Apr 09 '20 at 01:01
  • It is amazing who people are voting for closing posts as not-focused, when these posts contain only a single question. What is the OP supposed to do, to cut their question in half? – Theodor Zoulias Apr 09 '20 at 11:00
  • 1
    @Zer0 FWIW I think your answer and our subsequent comments discussing it captures the issue here pretty well and presents some options (none of which are perfect or ideal). I don't think every question posed here needs to be able to be answered perfectly. I asked this question partially because I was hopeful someone else might know something I don't, but also because when Googling for this I didn't ever really find anything that was relevant to what I was trying to do; maybe now others that have this problem will find this question and see the discussion around it. – Jesse Taber Apr 09 '20 at 14:22
  • Did you find any solution? How about `Image image = await Task.Run(() => Image.FromFile(FILE_PATH));`? – Theodor Zoulias Apr 14 '20 at 04:32

2 Answers2

2

You could try using the BitmapImage class. It has events for progress and failure/success. I think if you use SetSourceAsync you can cancel, I haven't played with this much though so I'm not sure if/how it handles multiple downloads at a time.

BoldAsLove
  • 660
  • 4
  • 13
1

Note this doesn't satisfy the requirements of reporting progress. That really needs to be handled by whatever is processing the image.

That said, why not use a Task and cancel it? While not gracious, it will stop you from doing needless work at least and satisfy the "able to cancel" requirement.

var cts = new CancellationTokenSource();
var task = Task.Run(() => LotsOfWork(), cts.Token);
//Causes the task to fail by an exception being thrown
cts.Cancel();

In an ideal world, LotsOfWork would also accept a CancellationToken and act appropriately.

Another solution (and I'm well aware this is not ideal) is to do this:

private void WrapperMethod(CancellationToken token)
{
    var currentThread = Thread.CurrentThread;
    using (token.Register(currentThread.Abort))
    {
        YourBlockingMethodHere();
    }
}

Important: the above uses Thread.Abort which is not good for many reasons I won't go into.

To do the above you need your own task scheduler that uses a dedicated thread.

If not, the thread you're aborting could be the caller's thread or a thread-pool thread. And that's very bad.

Is this a hack? Yes. But it may help in this case.

Zer0
  • 7,191
  • 1
  • 20
  • 34
  • Unless `LotsOfWork()` is able to look at `cts.Token` periodically to see if cancellation has been requested, then passing it the token doesn't really help. In my case `LotsOfWork()` is just `Image.FromFile(...)` and I have no control over what happens inside that function; I can't check to see if cancellation was requested. – Jesse Taber Apr 08 '20 at 14:50
  • @JesseTaber See my edit. – Zer0 Apr 08 '20 at 14:52
  • `task.Wait()` may throw, but in my case the `Image.FromFile` would still be chugging along holding a lock to the image file on disk which is what I want to avoid. – Jesse Taber Apr 08 '20 at 14:55
  • @JesseTaber I'll edit with another option that works for that case. – Zer0 Apr 08 '20 at 14:59
  • yah, the `Thread.Abort` option did occur to me, but that'd be a last resort. Thanks. – Jesse Taber Apr 08 '20 at 15:06
  • @JesseTaber I agree. A better option could be writing your own verison of `Image.FromStream` referencing the [code](https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Image.cs) or something similar. Basically, rolling out your own code. – Zer0 Apr 08 '20 at 15:10
  • The [`Thread.Abort`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.abort) is not supported on the .NET Core platform (throws `PlatformNotSupportedException`), so it's not an option going forward (upcoming .NET 5 etc). – Theodor Zoulias Apr 08 '20 at 16:27
  • 1
    @TheodorZoulias I completely agree. This is not ideal, as mentioned, but maybe something useful as a stopgap. If I ever used something like this in my own code it would be marked with "//TODO rewrite this horrific code". – Zer0 Apr 09 '20 at 01:02