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

John Torjo 861 Reputation points
2019-12-02T20:48:23.267+00:00

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.

Universal Windows Platform (UWP)
0 comments No comments
{count} vote

Accepted answer
  1. Richard Zhang-MSFT 6,936 Reputation points
    2019-12-03T01:55:24.2+00:00

    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.


1 additional answer

Sort by: Most helpful
  1. John Torjo 861 Reputation points
    2019-12-03T12:08:16.497+00:00

    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!