UI 线程和 Backgroundworker 出现问题

Trouble with UI Threads and Backgroundworker

我要实现的目标很简单。我有一个动态计时器(一个可以由用户更改的计时器),它调用后台工作人员去获取用户的外部 IP 地址。 TimerBackgroundWorker 的组合导致了一些问题。这是代码:

namespace IPdevices
{
    /// <summary>
    /// Interaction logic for Main.xaml
    /// </summary>
    public partial class Main : Window
    {

        private readonly BackgroundWorker worker;

        private IPret iprep;

        private Timer timer;


        public Main(Client client)
        {

            InitializeComponent();

            iprep = new IPret();

            startClock();

            worker  = new BackgroundWorker();
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += worker_ProgressChanged;

        }

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            ipAdd.Content = e.UserState;
        }

        private void startClock()
        {
            timer = new Timer();
            timer.Interval = 2000;
            timer.Elapsed += new ElapsedEventHandler(clockTimer_Tick);
            timer.Start();
        }

        private void clockTimer_Tick(object sender, ElapsedEventArgs e)
        {
            timer.Stop();
            worker.RunWorkerAsync();
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Console.WriteLine("Checking ip");
            iprep.refresh();
            worker.ReportProgress(0, iprep.getExternalIp());
            Console.WriteLine("Found ip");
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            timer.Start();
        }

    }
}

基本上,一旦计时器触发,我希望获取 ip 地址并在应用程序的标签上输出。但是,我在 ProgressChanged 方法中得到一个异常,说它不能被更改,因为另一个线程拥有它。那是哪个线程?它是属于另一个线程的 iprep 吗?事实上,RunWorkerCompleted 永远不会被解雇。我无法理解哪些线程拥有什么以及对象如何被锁定...任何见解将不胜感激。

如果我没记错的话,

ipAdd 是一个 UI 元素。如果是,那么问题出在交叉线程上。

发生的事情是后台工作人员将 运行 在与 UI 线程不同的线程上。如果你想修改 UI 元素的 属性,你需要在 UI 线程上进行。一种选择是使用 Dispatcher.Invoke 但由于您使用的是 WPF,因此有更好的方法来实现。

搜索一下 MVVM 设计模式并将后台代码移到视图模型中。然后你可以做类似

的事情
string _XXContent 
public string XXContent 
{
    get
    {
        return _XXContent;
    }
    set
    {
        _XXContent = value;
        OnPropertyChanged("XXContent");
    }
}


 private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
 {
     XXContent = e.UserState;
 }

xaml :

<TextBox Content={Binding XXContent}/>

编辑: 如果您使用的是 c# 5,那么您应该查看 async/IProgress 以及摆脱后台工作者。

这似乎在我的测试中修复了它

    private void clockTimer_Tick(object sender, ElapsedEventArgs e)
    {
        timer.Stop();
        Action a = () =>
        {
            worker.RunWorkerAsync();
        };
        Application.Current.Dispatcher.BeginInvoke(a);
    }

此外,我会注意到这是 WPF 中 Timer 的一致行为(我以前没有在 WPF 中使用过它);在 clockTimer_Tick 中尝试 ipAdd.Content = "Tick"; 会导致相同的错误。 System.Timers.Timer 的 tick 事件不会在 UI 线程上发生。

用下面显示的几行代码替换您的所有代码。 Tick 处理程序在 UI 线程中执行。它仍然异步运行后台操作并且不会阻塞 UI 线程。

private void StartClock()
{
    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
    timer.Tick += async (o, e) => await GetIP();
    timer.Start();
}

private async Task GetIP()
{
    Debug.WriteLine("Checking ip");

    await Task.Run(() =>
    {
        // Get the IP asynchronously here
    });

    Debug.WriteLine("Found ip");

    // Update the UI here
}