Question RPC_E_WRONG_THREAD Exception

  • Thursday, March 15, 2012 8:23 PM
     
      Has Code

    Hey folks.

    I'm having a problem that appears to result from a defect in the async/await/RunAsync() plumbing, but I'm looking for some pointers on if it's actually due to an error on my part, and/or if there's some kind of workaround until the defect is corrected.

    The basic idea is this: I have a class called ImageFinder, which searches for image files in a specified path (and all subdirs).  It exposes a collection of FoundImage instances, which is created and filled by a call to ImageFinder.FindImagesAsync().  The FoundImage class exposes properties for attributes like width, height, path, orientation, etc. and GetImageAsync(), which returns a BitmapImage instance created from the contents of the file.  It also caches its result so that subsequent calls return immediately/synchronously.

    What I want to do is have the FindImagesAsync() method fill the collection of FoundImage instances, and then start a background thread to call GetImageAsync() on every FoundImage instance.  The idea is that when the client code in the UI thread goes to retrieve and use the BitmapImage values via FoundImage.GetImageAsync(), there is a high probability that the background thread will have already loaded it.

    Here are some samples of the code:

    //
    // FoundImage class fragment
    //
    public class FoundImage
    {
        public async Task<BitmapImage> GetImageAsync()
        {
            if (_image == null)
            {
                IRandomAccessStream imageStream = await _imageFile.OpenAsync(FileAccessMode.Read);
                _image = new BitmapImage();
                _image.DecodePixelHeight = _decodeHeight;
                _image.SetSource(imageStream);
            }
            return _image;
        }
    }
    
    
    //
    // ImageFinder class fragment
    //
    public class ImageFinder
    {
        public async Task FindImagesAsync()
        {
            // Stuff to construct the query
    
            IReadOnlyList<StorageFile> imageFiles = await imageFileQuery.GetFilesAsync();
    
            if ((imageFiles != null) && (imageFiles.Count > 0))
            {
                _foundImages = new List<FoundImage>();
                int curImageCount = 0;
    
                foreach (StorageFile curImageFile in imageFiles)
                {
                    _foundImages.Add(await FoundImage.FromStorageFileAsync(curImageFile, _decodeHeight));
                }
            }
            else
            {
                _foundImages = null;
            }
            
            // Start the background thread to preload and cache the BitmapImage objects
            _cacheLoadAction = ThreadPool.RunAsync(new WorkItemHandler((IAsyncAction source) =>
                {
                    preloadCache(source);
                }
                ));
        }
    
        private async void preloadCache(IAsyncAction source)
        {
            int curImageCount = 0;
            foreach (FoundImage curImage in _foundImages)
            {
                if (source.Status == AsyncStatus.Canceled)
                {
                    break;
                }
                else
                {
                    await curImage.GetImageAsync();
                }
            }
        }
    
    }

    When the background thread calls curImage.GetImageAsync(), I get the following exception:

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

    Note that I can access other properties on curImage in this same method, so I suspect that the problem lies with the Task<BitmapImage> object returned from GetImageAsync(), not with the curImage instance itself.

    Here are my questions:

    1. Am I doing something wrong here, or is there a defect in the platform?
    2. What are my options for either a fix or workaround?
    3. While I'm at it, what's the best way to synchronize the implementation of FoundImage.GetImageAsync()?  I found that I can't use a lock statement for code that contains an await, and MethodImplOptions.Synchronized seems to no longer be supported - has it been replaced by something else?  Is a Mutex the right way to go here, despite the performance penalty?

    Thanks for any help/pointers/advice.

All Replies

  • Saturday, March 17, 2012 9:20 PM
     
     

    Hey again folks.  After further experimentation, I've found that what seems to really be causing the trouble is the BitmapImage class.

    I tried putting the instantiation and initialization of the BitmapImage instance in several places, and found that any reference to the instance outside of the UI thread results in this exception.  I can't even create an instance in my background thread.  So what I've got right now as a workaround is:

    1. Instance creation and setting of DecodePixelHeight property are done in the constructor.
    2. I have exposed a public method FoundImage.SetImageSource() that just calls BitmapImage.SetSource() to the opened stream passed in.
    3. I open the stream in my background thread and pass it to FoundImage.SetImageSource(), which is run in the UI thread via SynchronizationContext.Post().

    It's gross, but it works for now.

    Is it really illegal to create or reference an instance of BitmapImage outside of the UI thread?  I have reviewed the documentation for this class, and it doesn't say anything about that.  To me this really looks like a defect.

  • Monday, March 19, 2012 7:34 AM
     
     
    No one?
  • Friday, April 06, 2012 4:12 PM
     
     

    I'm also running into this issue when trying to create a bitmap from a stream.

    Phil, how did you get a SynchronizationContext to call Post on?  I've tried setting the SyncContext in the UI thread and then calling Post on it inside the async method but I still get the same exception.


    -- Mendel

  • Tuesday, April 10, 2012 4:09 AM
     
     

    Try creating BitmapImage by scheduling it in UI thread when required

    Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.Invoke(Windows.UI.Core.CoreDispatcherPriority.Normal, (s, a) => {
        if (_image == null)
            {
                IRandomAccessStream imageStream = await _imageFile.OpenAsync(FileAccessMode.Read);
                _image = new BitmapImage();
                _image.DecodePixelHeight = _decodeHeight;
                _image.SetSource(imageStream);
            }
    }, null, null);

    return _image;

  • Tuesday, April 10, 2012 8:20 AM
     
     

    The sender parameter can't be null but apart from that it schedules onto the UI thread as required.

    Any news on whether this constraint is likely to remain as a on the BitmapImage and WriteableBitmap classes?


    -- Mendel

  • Tuesday, April 10, 2012 7:42 PM
     
     

    Vikram,

    The thing is that certain scenarios require expensive stream reading or decoding to be done on worker thread.

    It's okay for BitmapImage or any DependencyObject to have thread affinity, but still it has to provide mechanism to be invoked from non-UI thread if it exposes certain expensive operations. I know this is tricky part due to constraints in COM apartment model.

    In WPF, BitmapImage could be both created and interacted on non-UI thread if frozen.

    It would be really nice to get official response regarding these topics.

    - ngm