Background worker,以及跨线程问题

3

我有一个Winforms应用程序,它正常工作。在处理与设备的串行数据时,使用BackgroundWorkerThread来管理GUI的可用性。

它一切正常。

现在,我正在添加一个新方法,并复制我在其他表单中所做的操作。但是我遇到了跨线程异常。

我像这样声明我的BWT:

BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += DownloadGpsDataFromDevice;
            bw.WorkerReportsProgress = true;
            bw.RunWorkerAsync();

我随后声明了一个方法,如下所示,该方法执行后台工作:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
    _performScreenUpdate = true;
    tsStatus.Text = "Downloading GPS Data...";
    Invalidate();
    Refresh();


    Common.WriteLog("Extracting raw GPS data. Sending LL.");

    ReplyString raw = DeviceServices.ExecuteCommand("$LL");

DeviceServices.ExecuteCommand("$LL"); 是实际执行操作的代码,但我在前一行记录到文本文件时出现了异常。这可能让你担心——写入文件。然而,在另一个BWT中,我已经做过数千次这样的操作。

我使写入线程安全。以下是我的Common.WriteLog方法:

public static void WriteLog(string input)
{
    lock (_lockObject)
    {
        WriteLogThreadSafe(input);
    }
}

private static void WriteLogThreadSafe(string input)
{
    Directory.CreateDirectory(LogFilePath);
    StreamWriter w = File.AppendText(LogFilePath + @"\" + LogFileName);
    try
    {
        w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
    }
    catch (Exception e)
    {
        System.Console.WriteLine("Error writing to log file!");
        System.Console.WriteLine("Tried to write: [" + input + "]");
        System.Console.WriteLine("Failed with error: [" + e.Message + "]");
    }
    finally
    {
        w.Close();
    }

}

这个一直运行良好。我不认为错误出在那里。也许是在调用时我漏掉了什么东西?

4个回答

6

在BackgroundWorker线程中,您无法更改UI元素。您需要通过调用Invoke()返回到UI线程。

试试这个

private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
    _performScreenUpdate = true;
    Invoke((MethodInvoker)(() => {
             tsStatus.Text = "Downloading GPS Data...";
             Invalidate();
             Refresh();
     });
     ...

那些代码行看起来还好...或者我收到了错误信息?它在Common.WriteLog("Extracting raw GPS data. Sending LL.");这一行崩溃了。 - Craig
等一下...你说得对,看起来是这样!应用程序在LogLine上崩溃了,但当我注释掉你提到的那些行时...它就可以工作了。不过很奇怪,它居然会在log行崩溃。谢谢! - Craig
@cdotlister WriteLog() 看起来没问题,不应该引起跨线程异常。在这种情况下我不确定。 - Bala R

0
你确定那是正确的代码行吗?我认为你不应该在工作者线程中更新界面。尝试注释掉GUI更新并清除并构建您的解决方案,以查看是否真正是日志记录引起的问题。要更新UI,请设置WorkerReportsProgress,在其中创建事件处理程序以更新UI并报告工作者进度。

0
问题在于您正在从非UI线程更新UI元素:
这些行不应该在 DownloadGpsDataFromDevice 中。
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();

为了利用BackgroundWorker的运行方法,请使用bw.ReportProgress(0);。请在ProgressChanged处理程序中更新UI,该处理程序专门为此目的而设计。
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (e.ProgressPercentage = 0)
    {
        tsStatus.Text = "Downloading GPS Data...";
        Invalidate();
        Refresh();
    }
}

这些代码行看起来没问题...或者我是不是收到了错误的提示?它在 Common.WriteLog("Extracting raw GPS data. Sending LL."); 处崩溃了。 - Craig
等一下...你说得对,看起来是这样!应用程序在LogLine上崩溃了,但当我注释掉你提到的那些行时...它就可以工作了。不过很奇怪,它居然会在log行崩溃。谢谢! - Craig
@cdotlister - 如果您不使用 ProgressChangedRunWorkerCompleted 事件,则不需要使用 if。这两个事件是 BackgroundWorker 的全部意义所在。这些事件始终在 UI 线程中触发。 - Alex Aza

0

有些实例不能或不应该被多个线程访问。您有两个选项来保护您的数据免受跨线程异常的影响。

您可以使用锁定(lock)在从多个线程访问时锁定对象:

object locker = new object();

SomeObject MyObject = new SomeObject();

private void FromMultipleThread()
{
    lock(locker)
    {
        MyObject = OtherObject;
    }
}

你的第二个选择是使用ManualResetEvent锁定线程。这非常简单,只需从你的ManualResetEvent调用WaitOne()来锁定线程,同时另一个线程访问你的“跨线程”对象。

在你的情况下,你需要从backgroundWorker的reportProgress中更改你的UI。reportProgress将返回到初始线程,然后你可以修改你的UI。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接