C# 为什么这会导致应用程序挂起
下面的代码导致我的WPF应用程序挂起(可能是死锁)。我已经验证了DownloadStringAsTask方法是在一个单独的(非UI)线程上执行的。有趣的是,如果您取消对messagebox行的注释(就在调用while(tasks.Any())之前),应用程序可以正常工作。有人能解释一下为什么应用程序会首先出错,以及如何解决这个问题吗C# 为什么这会导致应用程序挂起,c#,.net,wpf,task-parallel-library,deadlock,C#,.net,Wpf,Task Parallel Library,Deadlock,下面的代码导致我的WPF应用程序挂起(可能是死锁)。我已经验证了DownloadStringAsTask方法是在一个单独的(非UI)线程上执行的。有趣的是,如果您取消对messagebox行的注释(就在调用while(tasks.Any())之前),应用程序可以正常工作。有人能解释一下为什么应用程序会首先出错,以及如何解决这个问题吗 <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.micr
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="9*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Frame x:Name="frame" Grid.Row="0" />
<StatusBar VerticalAlignment="Bottom" Grid.Row="1" >
<StatusBarItem>
<TextBlock Name="tbStatusBar" Text="Waiting for getting update" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
public partial class MainWindow : Window
{
List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon124.com",
"http://www.msn.com"
};
public MainWindow()
{
InitializeComponent();
ProcessURLs();
}
public void ProcessURLs()
{
var tasks = URLsToProcess.AsParallel().Select(uri => DownloadStringAsTask(new Uri(uri))).ToArray();
//MessageBox.Show("this is doing some magic");
while (tasks.Any())
{
try
{
int index = Task.WaitAny(tasks);
this.tbStatusBar.Text = string.Format("{0} has completed", tasks[index].AsyncState.ToString());
tasks = tasks.Where(t => t != tasks[index]).ToArray();
}
catch (Exception e)
{
foreach (var t in tasks.Where(t => t.Status == TaskStatus.Faulted))
this.tbStatusBar.Text = string.Format("{0} has completed", t.AsyncState.ToString());
tasks = tasks.Where(t => t.Status != TaskStatus.Faulted).ToArray();
}
}
}
private Task<string> DownloadStringAsTask(Uri address)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(address);
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
tcs.SetException(args.Error);
else if (args.Cancelled)
tcs.SetCanceled();
else
tcs.SetResult(args.Result);
};
client.DownloadStringAsync(address);
return tcs.Task;
}
}
公共部分类主窗口:窗口
{
List URLsToProcess=新列表
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon124.com",
"http://www.msn.com"
};
公共主窗口()
{
初始化组件();
processURL();
}
公共void processURL()
{
var tasks=URLsToProcess.AsParallel().Select(uri=>DownloadStringAsTask(新uri(uri)).ToArray();
//Show(“这是在变魔术”);
while(tasks.Any())
{
尝试
{
int index=Task.WaitAny(任务);
this.tbStatusBar.Text=string.Format(“{0}已完成”,任务[index].AsyncState.ToString());
tasks=tasks.Where(t=>t!=tasks[index]).ToArray();
}
捕获(例外e)
{
foreach(tasks.Where中的var t(t=>t.Status==TaskStatus.Faulted))
this.tbStatusBar.Text=string.Format(“{0}已完成”,t.AsyncState.ToString());
tasks=tasks.Where(t=>t.Status!=TaskStatus.Faulted).ToArray();
}
}
}
私有任务下载StringAsTask(Uri地址)
{
TaskCompletionSource tcs=新TaskCompletionSource(地址);
WebClient客户端=新的WebClient();
client.DownloadStringCompleted+=(发送方,参数)=>
{
如果(args.Error!=null)
tcs.SetException(参数错误);
否则如果(参数已取消)
setCancelled();
其他的
tcs.SetResult(args.Result);
};
client.DownloadStringAsync(地址);
返回tcs.Task;
}
}
挂起的可能原因是您混合使用了sync和asnyc代码并调用WaitAny。斯蒂芬·克利里(Stephen Cleary)的文章有助于理解任务的常见问题。
下面是一个简化代码并使用Parallel.ForEach的解决方案
代码
public partial class WaitAnyWindow : Window {
private List<string> URLsToProcess = new List<string>
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon.com",
"http://www.msn.com"
};
public WaitAnyWindow02() {
InitializeComponent();
Parallel.ForEach(URLsToProcess, (x) => DownloadStringFromWebsite(x));
}
private bool DownloadStringFromWebsite(string website) {
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
if (e.Error != null)
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} didn't complete because {1}", website, e.Error.Message);
}));
}
else
{
Dispatcher.BeginInvoke((Action)(() =>
{
this.tbStatusBar.Text = string.Format("{0} has completed", website);
}));
}
};
client.DownloadStringAsync(new Uri(website));
return true;
}
}
public分部类WaitAnyWindow:Window{
私有列表URLsToProcess=新列表
{
"http://www.microsoft.com",
"http://www.stackoverflow.com",
"http://www.google.com",
"http://www.apple.com",
"http://www.ebay.com",
"http://www.oracle.com",
"http://www.gmail.com",
"http://www.amazon.com",
"http://www.outlook.com",
"http://www.yahoo.com",
"http://www.amazon.com",
"http://www.msn.com"
};
公共WaitAnyWindow02(){
初始化组件();
Parallel.ForEach(URLsToProcess,(x)=>DownloadStringFromWebsite(x));
}
私人bool下载StringFromWeb(字符串网站){
WebClient客户端=新的WebClient();
client.DownloadStringCompleted+=(s,e)=>
{
如果(例如错误!=null)
{
Dispatcher.BeginInvoke((操作)(()=>
{
this.tbStatusBar.Text=string.Format(“{0}未完成,因为{1}”,网站,e.Error.Message);
}));
}
其他的
{
Dispatcher.BeginInvoke((操作)(()=>
{
this.tbStatusBar.Text=string.Format(“{0}已完成”,网站);
}));
}
};
下载StringAsync(新Uri(网站));
返回true;
}
}
这里最大的问题是,在所有任务完成之前,构造函数不会返回。在构造函数返回之前,窗口不会显示,因为不会处理与绘制窗口相关的窗口消息
请注意,这里实际上并没有“死锁”,相反,如果您等待足够长的时间(即直到所有任务完成),实际上会显示窗口
添加对MessageBox.Show()
的调用时,用户界面线程有机会处理窗口消息队列。也就是说,正常模式对话框包括一个线程消息泵,它最终处理队列中的那些消息,包括与显示窗口相关的消息。请注意,即使添加MessageBox.Show()
,这不会导致在处理过程中更新窗口。它只允许在再次阻止UI线程之前显示窗口
解决此问题的一种方法是切换到异步
/等待
模式。例如:
public MainWindow()
{
InitializeComponent();
var _ = ProcessURLs();
}
public async Task ProcessURLs()
{
List<Task<string>> tasks = URLsToProcess.Select(uri => DownloadStringAsTask(new Uri(uri))).ToList();
while (tasks.Count > 0)
{
Task<string> task = await Task.WhenAny(tasks);
string messageText;
if (task.Status == TaskStatus.RanToCompletion)
{
messageText = string.Format("{0} has completed", task.AsyncState);
// TODO: do something with task.Result, i.e. the actual downloaded text
}
else
{
messageText = string.Format("{0} has completed with failure: {1}", task.AsyncState, task.Status);
}
this.tbStatusBar.Text = messageText;
tasks.Remove(task);
}
tbStatusBar.Text = "All tasks completed";
}
public主窗口()
{
初始化组件();
var=ProcessURLs();
}
公共异步任务ProcessURLs()
{
List tasks=URLsToProcess.Select(uri=>DownloadStringAsTask(newuri(uri))).ToList();
而(tasks.Count>0)
{
任务=等待任务。当有(任务);
字符串消息文本;
if(task.Status==TaskStatus.RanToCompletion)
{
messageText=string.Format(“{0}已完成”,task.AsyncState);
//TODO:对task.Result执行某些操作,即实际下载的文本
}
其他的
{
messageText=string.Format(“{0}”已完成,但失败:{