UI线程和后台工作者的麻烦

时间:2016-08-12 18:25:54

标签: c# wpf visual-studio 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永远不会被解雇。我无法理解哪些线程拥有什么以及如何锁定对象......任何洞察都会受到赞赏。

3 个答案:

答案 0 :(得分:2)

这似乎可以在我的测试中解决它

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

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

答案 1 :(得分:1)

如果我没有弄错的话,

ipAdd是一个UI元素。如果是,则问题在于交叉线程。

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

搜索MVVM设计模式并将后台代码移动到View Model中。然后你可以做类似

的事情
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以及摆脱背景工作者。

答案 2 :(得分:1)

将所有代码替换为下面显示的几行代码。 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
}