C# 无效的跨线程访问问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1924408/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 21:54:13  来源:igfitidea点击:

Invalid cross-thread access issue

c#silverlightmultithreadingsilverlight-3.0

提问by Ender

I have two ViewModel classes : PersonViewModel and PersonSearchListViewModel. One of the fields PersonViewModel implements is a profile image that is downloaded via WCF(cached locally in isolated storage). The PersonSearchListViewModel is a container class that holds a list of Persons. Since loading images is relatively heavy, PersonSearchListViewModel loads only images for the current, next and previous page (results are paged on UI)...to further improve the load of images I put the load of images on another thread. However multi-threading approach causes cross-thread access issues.

我有两个 ViewModel 类:PersonViewModel 和 PersonSearchListViewModel。PersonViewModel 实现的字段之一是通过 WCF 下载的个人资料图像(本地缓存在独立存储中)。PersonSearchListViewModel 是一个包含人员列表的容器类。由于加载图片比较繁重,PersonSearchListViewModel 只加载当前、下一页和上一页的图片(结果在 UI 上分页)……为了进一步改善图片的加载,我将图片加载到另一个线程上。然而,多线程方法会导致跨线程访问问题。

PersonViewModel :

人物视图模型:

public void RetrieveProfileImage()
{
    Image profileImage = MemorialDataModel.GetImagePerPerson(Person);
    if (profileImage != null)
    {
        MemorialDataModel.ImageManager imgManager = new MemorialDataModel.ImageManager();
        imgManager.GetBitmap(profileImage, LoadProfileBitmap);
    }
}

private void LoadProfileBitmap(BitmapImage bi)
{
    ProfileImage = bi;
    // update 
    IsProfileImageLoaded = true;
}

private BitmapImage profileImage;
public BitmapImage ProfileImage
{
    get
    {
        return profileImage;
    }
    set
    {
        profileImage = value;
        RaisePropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("ProfileImage"));
    }
}

PersonSearchListViewModel :

PersonSearchListViewModel :

private void LoadImages()
{
    // load new images 
    Thread loadImagesThread = new Thread(new ThreadStart(LoadImagesProcess));
    loadImagesThread.Start();

    //LoadImagesProcess(); If executed on the same thread everything works fine 
}

private void LoadImagesProcess()
{
    int skipRecords = (PageIndex * PageSize);
    int returnRecords;

    if (skipRecords != 0)
    {
        returnRecords = 3 * PageSize; // page before, cur page and next page 
    }
    else
    {
        returnRecords = 2 * PageSize;   // cur page and next page 
    }

    var persons = this.persons.Skip(skipRecords).Take(returnRecords);

    // load images 
    foreach (PersonViewModel pvm in persons)
    {
        if (!pvm.IsProfileImageLoaded)
        {
            pvm.RetrieveProfileImage();
        }
    }
}

How do you process data in ViewModel class in multi-threaded manner ? I know you have to use dispatcher on UI to update. How do you update ViewModel that is bound to UI ?

您如何以多线程方式处理 ViewModel 类中的数据?我知道你必须在 UI 上使用调度程序来更新。你如何更新绑定到 UI 的 ViewModel?

** EDIT **

** 编辑 **

There is also one more weird error happening. In the code below :

还有一个更奇怪的错误发生。在下面的代码中:

        public void GetBitmap(int imageID, Action<BitmapImage> callback)
        {
            // Get from server 
            bitmapCallback = callback;

            memorialFileServiceClient.GetImageCompleted += new EventHandler<GetImageCompletedEventArgs>(OnGetBitmapHandler);
            memorialFileServiceClient.GetImageAsync(imageID);
        }

        public void OnGetBitmapHandler(object sender, GetImageCompletedEventArgs imageArgs)
        {
            if (!imageArgs.Cancelled)
            {
                // I get cross-thread error right here 
                System.Windows.Media.Imaging.BitmapImage bi = new BitmapImage();
                ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image);

                // call call back
                bitmapCallback.Invoke(bi);
            }
        }

I get a cross-thread error when trying to create a new BitmapImage object in background thread. Why can't I create a new BitmapImage object on a background thread ?

尝试在后台线程中创建新的 BitmapImage 对象时出现跨线程错误。为什么我不能在后台线程上创建一个新的 BitmapImage 对象?

采纳答案by Phil

In order to update a DependencyProperty in a ViewModel, use the same dispatcher you would use to access any other UIElement:

为了更新 ViewModel 中的 DependencyProperty,请使用您用来访问任何其他 UIElement 的相同调度程序:

System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});

Also, BitmapImages have to be instantiated on the UI thread. This is because it uses DependencyProperties, which can only be used on the UI thread. I have tried instantiating BitmapImages on separate threads and it just doesn't work. You could try to use some other means to store images in memory. For example, when you download the image, store it in a MemoryStream. Then a BitmapImage on the UI thread can set its source to the MemoryStream.

此外,BitmapImages 必须在 UI 线程上实例化。这是因为它使用了 DependencyProperties,它只能在 UI 线程上使用。我曾尝试在单独的线程上实例化 BitmapImages,但它不起作用。您可以尝试使用其他一些方法将图像存储在内存中。例如,当您下载图像时,将其存储在 MemoryStream 中。然后 UI 线程上的 BitmapImage 可以将其源设置为 MemoryStream。

You may try instantiating the BitmapImages on the UI thread and then do everything else with the BitmapImage on another thread... but that would get hairy and I'm not even sure it would work. The following would be an example:

您可以尝试在 UI 线程上实例化 BitmapImages,然后在另一个线程上使用 BitmapImage 执行其他所有操作……但这会变得很麻烦,我什至不确定它是否会起作用。下面是一个例子:

System.Windows.Media.Imaging.BitmapImage bi = null;
using(AutoResetEvent are = new AutoResetEvent(false))
{
    System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        bi = new BitmapImage();
        are.Set();
    });
    are.WaitOne();
}

ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image);
bitmapCallback.Invoke(bi);

回答by Scott P

I believe you are having a cross threading issue with the UI thread.

我相信您的 UI 线程存在交叉线程问题。

Editing the bound object may force an update of the UI on the worker thread, which cannot succeed. You will likely need to do the InvokeRequired/Invoke hokey-pokey whenever you update the bound class.

编辑绑定对象可能会强制更新工作线程上的 UI,这不会成功。每当您更新绑定类时,您可能都需要执行 InvokeRequired/Invoke hokey-pokey。

You said you knew this already, but for reference:

您说您已经知道这一点,但仅供参考:

MSDN on thread-safe calls to UI

MSDN 关于对 UI 的线程安全调用

回答by Ernest

It can be achieved with WriteableBitmap.

它可以通过 WriteableBitmap 来实现。

    public void LoadThumbAsync(Stream src, 
                    WriteableBitmap bmp, object argument)  
    {  
        ThreadPool.QueueUserWorkItem(callback =>  
        {  
            bmp.LoadJpeg(src);  
            src.Dispose();  
            if (ImageLoaded != null)  
            {  
                Deployment.Current.Dispatcher.BeginInvoke(() =>  
                {  
                    ImageLoaded(bmp, argument);  
                });  
            }  
        });  
    }

But You have to construct WriteableBitmap in UI Thread, then loading can be performed in other thread.

但是你必须在UI线程中构造WriteableBitmap,然后才能在其他线程中进行加载。

    void DeferImageLoading( Stream imgStream )  
    {  
        // we have to give size  
        var bmp = new WriteableBitmap(80, 80);  
        imageThread.LoadThumbAsync(imgStream, bmp, this);  
    }  

See more explanaition on this blog post

在此博客文章中查看更多说明