在WPF窗口中,在控制台应用程序中启动,我尝试了这个例子,以便在async-await中使用IProgress:
namespace Test
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Main : Window
{
// class level declaration
Progress<string> Progress1;
public Main()
{
InitializeComponent();
// initialization of class level field
Progress1 = new Progress<string>(value =>
{
Print(value);
});
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
Print("Start test");
// local variable
Progress<string> Progress2 = new Progress<string>(value =>
{
Print(value);
});
// interface (normally Progress1/2 would be passed as a parameter)
// Progress2 works fine, Progress1 gives an exception
var progress = Progress1 as IProgress<string>;
await Task.Run(() =>
{
for (int i = 0; i != 5; ++i)
{
if (progress != null) progress.Report("\nStage " + i);
Thread.Sleep(1000);
}
});
Print("\nCompleted.");
}
void Print(string text)
{
//Output is a WPF TextBlock
Output.Inlines.Add(new Run(text) { Foreground = Brushes.Blue, FontWeight = FontWeights.Normal });
}
}
局部变量Progress2
工作正常:回调位于UI线程中
类级别字段Progress1
给出了一个例外。这个回调显然在后台线程中。
调用线程无法访问此对象,因为它不同 线程拥有它。
这可能与匿名函数和回调的工作方式有关 有人能解释一下这个问题吗?
EDIT
在正常的WPF解决方案中,问题无法重现
它可以在控制台应用程序中重现,其中WPF窗口的启动方式如下:
Application app = new Application ();
app.Run(new Main());
在这种情况下Synchronization.Current == null
在构造函数中,!= null
在{ Loaded
事件。正如答案和评论中所解释的那样。
答案 0 :(得分:4)
是的,购买Microsoft雪茄用于在Winforms中构建此诊断程序,因为否则会出现完全不可解决的问题,只会偶尔崩溃您的代码。
这是初始化顺序问题,它是由您的Progress变量初始化太快引起的。请记住Progress&lt;&gt; class,它知道如何运行你在“正确的线程”上传递的委托的目标。特别是,您希望它在您的UI线程上运行,以便您可以安全地更新标签。进展&LT;&GT;通过制作SynchronizationContext.Current
的副本,稍后使用它来使用Post()方法运行委托目标。
问题是,Current
属性还没有值。这种情况发生在几微秒之后,当Form基类构造函数运行时。进展&LT;&GT;复制了null,它现在唯一能做的就是在线程池线程上运行Post()方法目标。 KABOOM!
修复很简单:
Progress<string> Progress;
public Form1() {
InitializeComponent();
Progress = new Progress<string>(value => {
label.Text = value;
});
}
现在它稍后被初始化,Synchronization.Current有一个值。