在后台线程正确加载文档 [英] Correctly loading a document on a background thread

查看:126
本文介绍了在后台线程正确加载文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我写的应用程序和一个我继承,我有一个愿望继续更好地了解在后台线程加载数据的线程安全问题。假设我有一个简单,单一窗口的Windows窗体应用程序使用Load按钮和的BackgroundWorker





按钮的点击处理程序调用 loadBackgroundWorker.RunWorkerAsync(),以及工人的的DoWork 处理程序创建和初始化类型文件其中,加载后,它存储在窗体的 LoadedDocument 属性的对象。在工人的 RunWorkerCompleted 处理程序,一个的MessageBox 显示 LoadedDocument 。我知道这一切很难想象,所以我包括完整的代码。对不起,它使这么长时间阅读问题



这里的窗体的代码:



 使用系统; 
使用System.ComponentModel;使用System.Windows.Forms的
;

命名空间BackgroundLoadTest
{
公共部分Form1类:表格
{
私人文件_loadedDocument;
公开文件LoadedDocument
{
得到
{
锁(本)
{
返回_loadedDocument;
}
}

{
锁(本)
{
_loadedDocument =价值;
}
}
}

公共Form1中()
{
的InitializeComponent();
loadBackgroundWorker.DoWork + =新DoWorkEventHandler(loadBackgroundWorker_DoWork);
loadBackgroundWorker.RunWorkerCompleted + =新RunWorkerCompletedEventHandler(loadBackgroundWorker_RunWorkerCompleted);
}

无效loadBackgroundWorker_DoWork(对象发件人,DoWorkEventArgs E)
{
文档D =新的文件();
d.Property1 =测试;
d.Property2 = 1;
d.Property3 = 2;
this.LoadedDocument = D;
}

无效loadBackgroundWorker_RunWorkerCompleted(对象发件人,RunWorkerCompletedEventArgs E)
{
MessageBox.Show(加载Property1 =文件+
LoadedDocument.Property1 +,Property2 =+
LoadedDocument.Property2 +,Property3 =+
LoadedDocument.Property3);
}

私人无效loadButton_Click(对象发件人,EventArgs五)
{
loadBackgroundWorker.RunWorkerAsync();
}
}
}



下面的代码为文件类:

 使用系统; 

命名空间BackgroundLoadTest
{
公共类文件
{
公共字符串Property1 {搞定;组; }
公共双Property2 {搞定;组; }
公众诠释Property3 {搞定;组; }
}
}



我的问题是:



什么线程安全/存储可见性的问题,你用这个代码中看到的,或者你会做什么不同的给定负载数据在后台线程,并最终使用加载的数据目标在UI线程?



LoadedDocument 属性足以锁定,以确保数据初始化在后台线程将是UI线程可见?是锁定必要吗?我真的想了解在后台线程加载复杂的文档,同时保持GUI响应看似很常见的问题,我知道这是棘手的问题。



编辑:是清楚,我最关心的是这里存储的知名度。我想,以确保工人完成时全部由后台线程完成的数据初始化成为可见的GUI线程。我不想陷入生活的CPU缓存的变化和剩余看不见其他CPU线程。我不知道怎么说出我的顾虑更好,因为他们仍然相当模糊了我。


解决方案

锁定在你的getter和setter方法什么都不做,分配引用类型变量是一个原子操作。




这是纯错误。锁定引入了内存屏障,从而阻止指令重新排序,使其他线程可见的缓存值。访问字段或属性在不同的线程(也访问字段)不同步不能保证总是工作并不能算的正确的代码



你在做什么正在访问来自您的后台线程和你的UI线程LoadedDocument属性。正如你已经实现了在那里锁定,这是正确的代码,将是线程安全的。



DoWorkEventArgs 参数你的 loadBackgroundWorker_DoWork 方法有一个结果应该被用于设置后台工作的结果属性。在 RunWorkerCompletedEventArgs.Result 属性,则可以用来访问此值。请尝试以下操作:

 无效loadBackgroundWorker_DoWork(对象发件人,DoWorkEventArgs E)
{
文档D =新文件();
d.Property1 =测试;
d.Property2 = 1;
d.Property3 = 2;
e.Result = D;
}

无效loadBackgroundWorker_RunWorkerCompleted(对象发件人,RunWorkerCompletedEventArgs E)
{
this.LoadedDocument =(文档)e.Result;
MessageBox.Show(文档加载Property1 =+
LoadedDocument.Property1 +,Property2 =+
LoadedDocument.Property2 +,Property3 =+
LoadedDocument。 Property3);
}

本教程是在方面最全面,最易懂资源.NET中多线程,我会强烈建议之一。您的问题将有答案 rel=\"nofollow\">。






编辑:的BackgroundWorker的是如何同步的东西



$ b $澄清b

不过,我很好奇在BackgroundWorker的,使得通过e.Result到GUI线程完全可见通过数据放置在什么魔法。




展望后台工作参考源>,它是不是真的明显的办法,结果线程之间的同步:

 私人无效WorkerThreadStart(对象参数)
{
对象workerResult = NULL;
异常错误= NULL;
布尔取消= FALSE;


{
DoWorkEventArgs doWorkArgs =新DoWorkEventArgs(参数);
OnDoWork(doWorkArgs);
如果(doWorkArgs.Cancel)
{
取消= TRUE;
}
,否则
{
workerResult = doWorkArgs.Result;
}
}
赶上(例外的例外)
{
错误=除外;
}

RunWorkerCompletedEventArgs E =
新RunWorkerCompletedEventArgs(workerResult,错误,取消);

asyncOperation.PostOperationCompleted(operationCompleted,E);
}

这发生在后台线程。然后最后一行编组回UI线程。再向下看在栈中,有没有锁陈述或其它同步的指令那里。所以,这是怎么线程安全的?



展望的 RunWorkerCompletedEventArgs ,我们要么找不到同步码。但有一些奇怪的属性在那里:

  [HostProtection(对sharedState =真)] 
公共类RunWorkerCompletedEventArgs:AsyncCompletedEventArgs

MSDN 解释道:




在对sharedState是真实的,它表明的状态下暴露了
可能线程之间共享。




所以,把你的类上面这个属性显然使得它的成员线程通过同步它们的访问安全。这是真棒?我想是这样。如果您在代码中使用呢?很可能不会。


From apps I've written and one I've inherited, I have a continuing desire to better understand the thread-safety issues of loading data on a background thread. Suppose I have a simple, single-window Windows Forms app with a "Load" button and a BackgroundWorker:

The button's Click handler calls loadBackgroundWorker.RunWorkerAsync(), and the worker's DoWork handler creates and initializes an object of type Document which, after loading, it stores in the form's LoadedDocument property. In the worker's RunWorkerCompleted handler, a MessageBox displays the properties of the LoadedDocument. I know this is all hard to visualize, so I'm including complete code. Sorry that it makes the question so long to read.

Here's the form's code:

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace BackgroundLoadTest
{
    public partial class Form1 : Form
    {
        private Document _loadedDocument;
        public Document LoadedDocument
        {
            get
            {
                lock (this)
                {
                    return _loadedDocument;
                }
            }
            set
            {
                lock (this)
                {
                    _loadedDocument = value;
                }
            }
        }

        public Form1()
        {
            InitializeComponent();
            loadBackgroundWorker.DoWork += new DoWorkEventHandler(loadBackgroundWorker_DoWork);
            loadBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(loadBackgroundWorker_RunWorkerCompleted);
        }

        void loadBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Document d = new Document();
            d.Property1 = "Testing";
            d.Property2 = 1;
            d.Property3 = 2;
            this.LoadedDocument = d;
        }

        void loadBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("Document loaded with Property1 = " +
                LoadedDocument.Property1 + ", Property2 = " +
                LoadedDocument.Property2 + ", Property3 = " +
                LoadedDocument.Property3);
        }

        private void loadButton_Click(object sender, EventArgs e)
        {
            loadBackgroundWorker.RunWorkerAsync();
        }
    }
}

Here's the code for the Document class:

using System;

namespace BackgroundLoadTest
{
    public class Document
    {
        public string Property1 { get; set; }
        public double Property2 { get; set; }
        public int Property3 { get; set; }
    }
}

My question is:

What thread-safety/memory-visibility problems do you see with this code, or what would you do differently given the goal of loading data on the background thread and eventually using the loaded data on the UI thread?

Is the locking in the LoadedDocument property sufficient to ensure that data initialized in the background thread will be visible to the UI thread? Is the locking necessary? I really want to understand the seemingly very common problem of loading complex documents on a background thread while keeping the GUI responsive, and I know it's tricky stuff.

Edit: to be clear, what I'm most concerned about here is memory visibility. I want to be sure that all the data initialization done by the background thread becomes visible to the GUI thread when the worker completes. I don't want changes getting stuck in a CPU cache and remaining invisible to threads on other CPUs. I don't know how to state my concerns better because they're still rather vague to me.

解决方案

Locking around your getters and setters do nothing, assigning a reference type to a variable is an atomic operation.

This is plain wrong. Locking introduces memory barriers and thereby prevents instruction reordering and makes cached values visible to other threads. Accessing fields or properties (which also access fields) from different threads without synchronization isn't guaranteed to always work and can't be considered correct code.

What you're doing is accessing the LoadedDocument property from both your background thread and your UI thread. As you have implemented locking in there, this is correct code and will be thread safe.

The DoWorkEventArgs argument in your loadBackgroundWorker_DoWork method has a Result property which should be used to set the result of the background work. The RunWorkerCompletedEventArgs.Result property then can be used to access this value. Try the following:

    void loadBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        Document d = new Document();
        d.Property1 = "Testing";
        d.Property2 = 1;
        d.Property3 = 2;
        e.Result = d;
    }

    void loadBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.LoadedDocument = (Document)e.Result;
        MessageBox.Show("Document loaded with Property1 = " +
            LoadedDocument.Property1 + ", Property2 = " +
            LoadedDocument.Property2 + ", Property3 = " +
            LoadedDocument.Property3);
    }

This tutorial is one of the most comprehensive and understandable resources in regard to multithreading in .NET which I would highly recommend. Your question would have been answered here.


Edit: Clarification of how BackgroundWorker synchronizes stuff

Still, I'm curious what magic goes on in BackgroundWorker that makes data passed via e.Result fully visible to the GUI thread.

Looking into reference source of Background worker, it is not really obvious how the result is synchronized between threads:

    private void WorkerThreadStart(object argument)
    {
        object workerResult = null;
        Exception error = null;
        bool cancelled = false;

        try
        {
            DoWorkEventArgs doWorkArgs = new DoWorkEventArgs(argument);
            OnDoWork(doWorkArgs);
            if (doWorkArgs.Cancel)
            {
                cancelled = true;
            }
            else
            {
                workerResult = doWorkArgs.Result;
            }
        }
        catch (Exception exception)
        {
            error = exception;
        }

        RunWorkerCompletedEventArgs e = 
            new RunWorkerCompletedEventArgs(workerResult, error, cancelled); 

        asyncOperation.PostOperationCompleted(operationCompleted, e);
    }

This happens on the background thread. The last line then marshals back to the UI thread. Looking further down the stack, there are no lock statements or other synchronization directives there. So how is this made thread safe?

Looking into the RunWorkerCompletedEventArgs, we find no synchronization code either. But there is some strange attribute over there:

[HostProtection(SharedState = true)]
public class RunWorkerCompletedEventArgs : AsyncCompletedEventArgs

MSDN explains:

When SharedState is true, it indicates that a state is exposed that might be shared between threads.

So putting this attribute above your class obviously makes its members thread safe by synchronizing their access. Is this awesome? I think so. Should you use this in your code? Probably not.

这篇关于在后台线程正确加载文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆