【问题标题】:Unable to modify variables from separate thread无法从单独的线程修改变量
【发布时间】:2015-06-20 08:26:03
【问题描述】:

所以我正在制作一个 C# 应用程序,它必须连续读取和显示文本文件的内容,同时允许用户在文本框中输入内容并将其附加到该文件的末尾。

我通过在单独的线程上运行我的read 方法来做到这一点,但是更改存储显示文本文件内容的变量是导致问题的原因。最初我尝试使用一种方法来执行此操作,但是这不起作用并给出了“跨线程操作无效”错误。然后我尝试应用我在 MSDN 上找到的一些代码,但现在在线程结束后更新变量之后!

请帮忙。

partial class MainForm
{
    delegate void SetTextCallback(string text);
    public static string msg;
    public static string name;
    public void InitClient()
    {
        name = "public.txt";
        Console.WriteLine(name);
        if(!File.Exists(name))
        {
            File.Create(name);
            File.AppendAllText(name, "Welcome to " + name);
        }
        Thread Read = new Thread(new ThreadStart(this.Client));
        Read.Start();
        while(!Read.IsAlive);
    }
    public void WriteText()
    {
        File.AppendAllText(name, this.InputBox.Text);
        this.InputBox.Clear();
    }
    private void SetText(string text)
    {
        if (this.OutPut.InvokeRequired)
        {    
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.OutPut.Text = text;
        }
    }
    public void Client()
    {
        msg = File.ReadAllText(name);
        Console.WriteLine(msg);
        Thread.Sleep(300);
        this.SetText(msg);
    }
}

为什么线程会这样。如何修改我的代码,使输出框的内容始终等于文本文件的内容。

欢迎提出任何建议。

【问题讨论】:

  • Client() 是一种一次性方法。当然它会在 1 个动作后终止,没有循环。
  • 这是正确的行为,任何与控件相关的数据都必须在 UI 线程上下文中更改,您不能在任何其他线程上执行此操作,因为我在 SetText 方法中看到以下代码将是一个问题- this.OutPut.Text = 文本;您正在尝试在单独的线程上修改 UI 控件
  • @MrinalKamboj - else 部分只有在(递归)调用后才会被命中。这里的SetText方法是正确的。
  • 另外 Thread.Join 是在另一个线程正在执行时停止 UI 线程的更好方法,不要使用 While 循环进行轮询。还有为什么不使用TPL,为什么还要依赖线程类来做这项工作

标签: c# multithreading winforms text file-io


【解决方案1】:

这里有很多问题,

  • 文件的使用可能不是线程安全的。
  • 您的方法不会重复
  • 你是Sleep()正在讨论一个话题

您可以通过放弃线程并使用简单的计时器来解决所有问题。

【讨论】:

  • 如果我重复该方法,应用程序将冻结,可能应该提到这一点。使用时间,我可以使它工作。谢谢
  • 或BackgroundWorker(见我的回答)
【解决方案2】:

尝试使用后台工作者而不是创建新线程。后台工作人员将在单独的线程中运行其内容,并允许您在其工作时报告“进度”。此进度报告将始终在 UI 线程(或启动后台工作程序的线程)上运行。

它还有一个在后台工作人员完成时调用的事件。这也在 UI 线程上运行。

这个例子应该让你开始。

更新:按照建议添加了一些非常基本的错误处理

我们的想法是在需要时使用 ReportProgress 的 UserData(第二个参数)在 UI 线程上进行更新。在这种情况下,它是一个字符串,但它可以是任何对象。

此外,您可以使用 DoWorkEventArgs 的 Result 从后台工作中生成最终结果。在这种情况下,我返回任何抛出的异常,否则返回 null,但您也可以在此处返回任何您想要的内容。

正如 Henk 在他的评论中提到的,处理 DoWork 回调中发生的错误非常重要,因为这里发生的异常等将被吞没,并且工作人员将完成,就好像没有发生任何不好的事情一样。

private BackgroundWorker _backgroundWorker;

public Form1()
{
    InitializeComponent();

    _backgroundWorker = new BackgroundWorker();
    _backgroundWorker.WorkerReportsProgress = true;
    _backgroundWorker.WorkerSupportsCancellation = true;
    // This is the background thread
    _backgroundWorker.DoWork += BackgroundWorkerOnDoWork;

    // Called when you report progress
    _backgroundWorker.ProgressChanged += BackgroundWorkerOnProgressChanged;

    // Called when the worker is done
    _backgroundWorker.RunWorkerCompleted += BackgroundWorkerOnRunWorkerCompleted;
}

private void BackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
    if (runWorkerCompletedEventArgs.Result != null)
    {
        // Handle error or throw it
        throw runWorkerCompletedEventArgs.Result as Exception;
    }

    textBox1.Text = "Worker completed";
}

private void BackgroundWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
    textBox1.Text = progressChangedEventArgs.UserState as string;
}

private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
    try
    {
        for (int i = 0; i < 100 && !_backgroundWorker.CancellationPending; i++)
        {
            _backgroundWorker.ReportProgress(0, i + " cycles");
            Thread.Sleep(100);
        }
    }
    catch (Exception ex)
    {
        doWorkEventArgs.Result = ex;
    }

}

private void startButton_Click(object sender, EventArgs e)
{
    if (!_backgroundWorker.IsBusy)
        _backgroundWorker.RunWorkerAsync();
}

private void cancelButton_Click(object sender, EventArgs e)
{
    if(_backgroundWorker.IsBusy)
        _backgroundWorker.CancelAsync();
}

【讨论】:

  • 那仍然是Sleep()s 在一个线程上,但这是一个小问题。省略所有错误处理更严重,这会导致“它不起作用但没有错误”。
  • @HenkHolterman 这只是后台工作人员如何工作的一个例子,那里的睡眠无关紧要。好点re:错误处理虽然会更新
  • 错误处理已经是 Bgw 的一部分,例如见my answer here
猜你喜欢
  • 1970-01-01
  • 2013-09-28
  • 2023-03-27
  • 1970-01-01
  • 2011-04-09
  • 2013-09-14
  • 2019-05-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多