question

jtorjo avatar image
0 Votes"
jtorjo asked ·

[uwp][c#] is there a way to load a bitmap, NOT on the main thread?

I have a .png file, and want to load it as a WriteableBitmap. However, if I don't do this on the main thread, I get an exception.

         var file = ...path_to_some_png_file...;
         Task.Run(async () => {
             using (var stream = new System.IO.FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                 var bmp = await BitmapFactory.FromStream(stream);
             }
         });


The exception is: System.Exception: 'The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))'

Is there a way to do this? I need to load hundreds of pictures during startup, and this bottleneck renders my app useless.

uwp
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

RichardZhang-MSFT avatar image
0 Votes"
RichardZhang-MSFT answered ·

Hello,​

Welcome to our Microsoft Q&A platform!

If you intend to access UI resources in a non-UI thread, use Dispatcher.RunAsync:

 await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
 {
     Task.Run(async () => {
         using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
         {
             var bmp = await BitmapFactory.FromStream(stream);
             // other code
         }
     });
 });
 

You mentioned that you need to save multiple pictures so that you can use Task for multi-tasking parallel processing.

 var tasks = new List();
 tasks.Add(Task.Run(async () =>
 {
     await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
     {
         using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
         {
             var bmp = await BitmapFactory.FromStream(stream);
             // other code
         }
     });
 }));
 await Task.WhenAll(tasks.ToArray());  

Task.WhenAll will assign tasks according to the current CPU's computing power, and can handle multiple tasks at the same time, which will greatly speed up your image task processing speed.

In this way, you can achieve your goals.

Thanks.

· 9 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Richard,

Thanks for the answer, but this does not answer my question - the main thread IS THE BOTTLENECK. Hundreds of bitmaps need to wait one after the other, because someone decided to make all loading/saving happen on the main thread.

IT IS A HUUUUUGE BOTTLENECK

Best, John

0 Votes 0 · ·

Hi, I update my answer, please check it.

0 Votes 0 · ·

Hi Richard,

I am well aware of running tasks in parallel. The fact that you run 1000 tasks in parallel, but each end up WAITING ON THE MAIN THREAD, is completely useless:

await Dispatcher.RunAsync -> this will run this code:

      using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
      {
          var bmp = await BitmapFactory.FromStream(stream);
          // other code
      }

on the main thread. So, we have 1000 tasks, that end up executing a RunAsync, which will then run SEQUENTIALLY on the main thread.

0 Votes 0 · ·

Hi,

The Image control is in the UI thread. If you want to assign BitmapImage to the Image.Source property, you must communicate with the main thread. In fact, you can extract the process of reading a file and generating a Bitmap from Dispatcher.RunAsync(), and only use Dispatcher.RunAsync when assigning value to Image.Source.

But I also want to ask, why don't you use Url to assign value to Image?

like:

 var bitmap = new BitmapImage(new Uri(url));
 image.Source = bitmap;
0 Votes 0 · ·
jtorjo avatar image jtorjo RichardZhang-MSFT ·

Hi Richard,

This is an incredibly simplistic view of the world you just outlined above. Sure, in slideshows and for people just take 2 or 3 bitmaps and play around with them, this sounds just great - it's easy even.

I need about two thousand 1280 x 720 images loaded before my application can start - those images then get to be sent to win2d, and I apply effects on them.

So please don't give me a simplistic solutions such as "image.Source = bitmap"

0 Votes 0 · ·
Show more comments
jtorjo avatar image
0 Votes"
jtorjo answered ·

Here's a class to test loading of bitmaps as SoftwareBitmap:

 class read_soft_bitmaps
 {
     private class soft_bitmap {
         public SoftwareBitmap soft_bmp;
         public double time_ms;
     }
     private List bitmaps_ = new List();
     private double time_ms_;

     public double time_ms => time_ms_;

     private Task read_file(StorageFile file) {
         return Task.Run(async () => {
             var watch = Stopwatch.StartNew();
             using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read))
             {
                 BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
                 // Get the SoftwareBitmap representation of the file
                 var bmp = await decoder.GetSoftwareBitmapAsync();
                 var ms = watch.ElapsedMilliseconds;
                 lock(this)
                     bitmaps_.Add(new soft_bitmap { soft_bmp = bmp, time_ms = ms});
             }
         });
     }

     public async Task read_files(IReadOnlyList file_names) {
         var watch = Stopwatch.StartNew();
         List files = new List();
         foreach ( var f in file_names)
             files.Add(await StorageFile.GetFileFromPathAsync(f));

         var tasks = files.Select(read_file).ToArray();
         await Task.WhenAll(tasks);
         time_ms_ = watch.ElapsedMilliseconds;
     }
 }

Here's my previous implementation, of loading files, and then transforming them to SoftwareBitmap on the main thread:

 class read_files_in_memory : IDisposable
 {
     private class file_info {
         public string file_name;
         public InMemoryRandomAccessStream stream;
         // just for testing
         public double read_ms = 0;
     }

     private List files_ = new List();
     private double time_ms_;

     public double time_ms => time_ms_;

     private Task read_file(string file_name) {
         return Task.Run(async () => {
             var watch = Stopwatch.StartNew();
             var bytes = File.ReadAllBytes(file_name);
             
             var fi = new file_info {
                 file_name = file_name,
                 stream = new InMemoryRandomAccessStream()
             };
             fi.stream.AsStreamForWrite().Write(bytes, 0, bytes.Length);
             fi.stream.Seek(0);
             fi.read_ms = watch.ElapsedMilliseconds;
             lock(this)
                 files_.Add(fi);
         });
     }

     public InMemoryRandomAccessStream file_stream(string file) {
         Trace.Assert(files_.Any(f => f.file_name == file));
         return files_.First(f => f.file_name == file).stream;
     }

     public async Task read_files(IReadOnlyList files) {
         var watch = Stopwatch.StartNew();
         var tasks = files.Select(read_file).ToArray();
         await Task.WhenAll(tasks);
         time_ms_ = watch.ElapsedMilliseconds;
     }


     public void Dispose() {
         lock(this)
             foreach (var f in files_)
                 f.stream.Dispose();
     }
 }

The solution Richard showed me is about 4-5 times faster than what I was using. Thanks Richard!

· 1 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Glad I can help you, and thank you for posting your own solution, this will help more people :)

1 Vote 1 · ·