在WPF中安全地访问UI(主)线程

120

我有一个应用程序,每当我正在监视的日志文件被更新(追加新文本)时,以以下方式更新我的数据网格:

private void DGAddRow(string name, FunctionType ft)
    {
                ASCIIEncoding ascii = new ASCIIEncoding();

    CommDGDataSource ds = new CommDGDataSource();

    int position = 0;
    string[] data_split = ft.Data.Split(' ');
    foreach (AttributeType at in ft.Types)
    {
        if (at.IsAddress)
        {

            ds.Source = HexString2Ascii(data_split[position]);
            ds.Destination = HexString2Ascii(data_split[position+1]);
            break;
        }
        else
        {
            position += at.Size;
        }
    }
    ds.Protocol = name;
    ds.Number = rowCount;
    ds.Data = ft.Data;
    ds.Time = ft.Time;

    dataGridRows.Add(ds); 

    rowCount++;
    }
    ...
    private void FileSystemWatcher()
    {
        FileSystemWatcher watcher = new FileSystemWatcher(Environment.CurrentDirectory);
        watcher.Filter = syslogPath;
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
            | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        watcher.EnableRaisingEvents = true;
    }

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (File.Exists(syslogPath))
        {
            string line = GetLine(syslogPath,currentLine);
            foreach (CommRuleParser crp in crpList)
            {
                FunctionType ft = new FunctionType();
                if (crp.ParseLine(line, out ft))
                {
                    DGAddRow(crp.Protocol, ft);
                }
            }
            currentLine++;
        }
        else
            MessageBox.Show(UIConstant.COMM_SYSLOG_NON_EXIST_WARNING);
    }

当FileWatcher触发事件时,由于它创建了一个单独的线程,因此当我尝试运行dataGridRows.Add(ds)来添加新行时,在调试模式下程序会崩溃而没有任何警告。

在Winforms中,可以通过使用Invoke函数轻松解决此问题,但我不确定如何在WPF中处理这个问题。

3个回答

263
你可以使用 Dispatcher.Invoke(Delegate, object[]) 在应用程序的(或任何UIElement的)调度程序上。
例如,你可以像这样使用它:
Application.Current.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));
或者
someControl.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));

1
上述方法报错的原因是在运行该行代码时Application.Current为空。为什么会出现这种情况? - TtT23
1
@l46kok 这可能有不同的原因(控制台应用程序,从winforms托管等)。正如@WolfgangZiegler所说,您可以使用任何UIElement来实现它。我通常只是使用Application.Current,因为我觉得这样更清晰。 - Botz3000
@Botz3000 我认为我这里也存在一些竞态条件问题。在添加了上述代码后,当我进入调试模式并手动执行步骤时,代码可以完美运行,但是当我在没有调试的情况下运行应用程序时,代码会崩溃。我不确定需要锁定哪个部分才会导致问题。 - TtT23
1
如果您认为这是死锁,您也可以调用Dispatcher.BeginInvoke。该方法只是将委托排队等待执行。 - Botz3000
太棒了!对我来说非常有效。但是,我会添加 DispatcherPriority.Normal 以防止任何意外的结果。如果没有它,例如,我的程序会抛出未处理的异常,表示任务被用户取消,并且导致程序崩溃。 - B.K.
显示剩余4条评论

68

最好的做法是从UI线程获取一个SynchronizationContext并使用它。这个类抽象了对其他线程的调用,使得测试更加容易(与直接使用WPF的Dispatcher相比)。例如:

class MyViewModel
{
    private readonly SynchronizationContext _syncContext;

    public MyViewModel()
    {
        // we assume this ctor is called from the UI thread!
        _syncContext = SynchronizationContext.Current;
    }

    // ...

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
         _syncContext.Post(o => DGAddRow(crp.Protocol, ft), null);
    }
}

请注意,通常不建议使用Send方法-这样会不必要地阻塞线程池线程等待UI更新,而不是使用异步方法在UI更新时等待(例如属性和集合更新的事件),并将该线程释放以执行其他工作。

非常感谢!被认可的解决方案在每次调用时都会挂起,但这个可以工作。 - Dov
它也可在包含视图模型但没有“真正”的WPF,即类库的程序集中被调用。 - Onur
这是一个非常有用的技巧,特别是当您拥有一个非 WPF 组件并且希望将操作转发到线程时。当然,另一种方法是使用 TPL 连续体。 - MaYaN
起初我不理解,但它对我起作用了.. 很好。 (应该指出DGAddRow是一个私有方法) - Tim Davis
4
您应该提到 _syncContext.Post(o => 是一种异步的“fire and forget”方式。要进行同步调用,应该使用 _syncContext.Send(o => - marsh-wiggle
1
另外,Stephen Cleary在他的书中不建议使用Dispatcher,因为它是特定于平台的。他推荐使用SynchronizationContext.Current(参见第13.2部分)。 - Rodion Mostovoi

9

使用[Dispatcher.Invoke(DispatcherPriority, Delegate)]在后台线程中或其他线程中更改UI。

步骤1。使用以下命名空间。

using System.Windows;
using System.Threading;
using System.Windows.Threading;

步骤2。将以下行添加到需要更新UI的位置

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate
{
    //Update UI here
}));

Syntax

[BrowsableAttribute(false)]
public object Invoke(
  DispatcherPriority priority,
  Delegate method
)

Parameters

priority

Type: System.Windows.Threading.DispatcherPriority

The priority, relative to the other pending operations in the Dispatcher event queue, the specified method is invoked.

method

Type: System.Delegate

A delegate to a method that takes no arguments, which is pushed onto the Dispatcher event queue.

Return Value

Type: System.Object

The return value from the delegate being invoked or null if the delegate has no return value.

Version Information

Available since .NET Framework 3.0


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