C# 跨线程属性行为

C# 跨线程属性行为,c#,multithreading,C#,Multithreading,我有一个带有文本框的windows窗体txtOutput。我有一些内容。我编写了一个属性来获取和设置同一线程内和跨线程的txtOutput.Text,如下所示: public string OutputString { get { string text = string.Empty; if (txtOutput.InvokeRequired) { txtOutput.BeginInvoke(new Meth

我有一个带有文本框的windows窗体
txtOutput
。我有一些内容。我编写了一个属性来获取和设置同一线程内和跨线程的
txtOutput.Text
,如下所示:

public string OutputString
{
    get
    {
        string text = string.Empty;
        if (txtOutput.InvokeRequired)
        {
            txtOutput.BeginInvoke(new MethodInvoker(delegate
                {
                    text = txtOutput.Text;
                }));
        }
        else
        {
            text = txtOutput.Text;
        }
        return text;
    }
    set
    {
        if (txtOutput.InvokeRequired)
        {
            txtOutput.BeginInvoke(new MethodInvoker(delegate
                {
                    txtOutput.Text = value;
                }));
        }
        else
        {
            txtOutput.Text = value;
        }
    }
}
如果我从同一线程设置/获取属性,则调用下面的函数(如
PrintMessage()
)时的行为与预期相同

但是当我像这样调用新线程(PrintMessage).Start()时。
get
不会检索文本框中的值(即
MessageBox
显示空字符串)。当我通过在行上保留断点来执行相同操作时:

txtOutput.BeginInvoke(new MethodInvoker(delegate
{
    text = txtOutput.Text;
}));
调试时,检索值(即
MessageBox
显示
txtOutput
内容)


我应该睡在什么地方吗?我在哪里出错了?

问题是,在UI线程能够处理您向dispatcher发出的请求之前,您正在调用MessageBox.Show()并引用文本变量。我会避免使用Thread.Sleep(),因为这样会产生一些严重的副作用。理想情况下,您应该重新考虑您的代码,以摆脱同步属性,转而采用更异步的解决方案。类似于下面代码的内容应该会给出您想要的结果:

public void PrintMessage(Action<string> displayAction)
{
    string text = string.Empty;
    if (txtOutput.InvokeRequired)
    {
        txtOutput.BeginInvoke(new MethodInvoker(delegate
        {
            displayAction.Invoke(txtOutput.Text);
        }));
    }
    else
    {
        displayAction.Invoke(txtOutput.Text);
    }
}
您可以将任务用于。当您将调度程序窗体UI线程传递给任务工厂时,此任务将在UI线程中执行,并将结果返回到创建此任务的线程

namespace WpfTest {
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

public partial class MainWindow : Window {
    private readonly TaskScheduler _taskScheduler;

    public MainWindow() {
        InitializeComponent();

        _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        button.Click += ButtonOnClick;
    }

    public string Text {
        get {
            if (text.CheckAccess()) {
                return text.Text;
            }
            return Task.Factory.StartNew(
                () => text.Text, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
        }
        set {
            if (text.CheckAccess()) {
                text.Text = value;
            } else {
                Task.Factory.StartNew(
                    () => { text.Text = value; }, CancellationToken.None, TaskCreationOptions.None, _taskScheduler);
            }
        }
    }

    private void ButtonOnClick(object sender, RoutedEventArgs routedEventArgs) {
        Text += "Test1";
        new Thread(() => { Text += "Test2"; }).Start();
    }
}
}

谢谢但是如何像这样实现
set
属性呢?我不推荐的唯一方法是通过调用属性的get访问器中的txtOutput.Invoke()而不是txtOutput.BeginInvoke()来阻止调用线程。它可以工作。但是为什么我不应该使用它呢?为什么不推荐使用它?如果使用它,代码应该可以正常工作,但通常假定属性get是相对便宜的操作。如果您开始在get访问器中阻塞,您可能会注意到性能下降,因为您的应用程序变得更加复杂,而其他程序员使用您的类,不知道这可能会阻塞线程。我尝试遵循的规则是,如果代价高昂,请将其放入方法调用中,并根据情况(例如,在用户界面和web应用程序的情况下)使这些方法调用异步。
// Get the text asynchronously
PrintMessage(s => MessageBox.Show(s));
namespace WpfTest {
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

public partial class MainWindow : Window {
    private readonly TaskScheduler _taskScheduler;

    public MainWindow() {
        InitializeComponent();

        _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        button.Click += ButtonOnClick;
    }

    public string Text {
        get {
            if (text.CheckAccess()) {
                return text.Text;
            }
            return Task.Factory.StartNew(
                () => text.Text, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
        }
        set {
            if (text.CheckAccess()) {
                text.Text = value;
            } else {
                Task.Factory.StartNew(
                    () => { text.Text = value; }, CancellationToken.None, TaskCreationOptions.None, _taskScheduler);
            }
        }
    }

    private void ButtonOnClick(object sender, RoutedEventArgs routedEventArgs) {
        Text += "Test1";
        new Thread(() => { Text += "Test2"; }).Start();
    }
}
}