我想要实现的目标很简单。我有一个动态计时器(一个可由用户更改),它调用后台工作程序去获取用户的外部IP地址。 Timer
和BackgroundWorker
的组合会导致一些问题。这是代码:
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
永远不会被解雇。我无法理解哪些线程拥有什么以及如何锁定对象......任何洞察都会受到赞赏。
答案 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
}