C# 为什么这会导致应用程序挂起

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

下面的代码导致我的WPF应用程序挂起(可能是死锁)。我已经验证了DownloadStringAsTask方法是在一个单独的(非UI)线程上执行的。有趣的是,如果您取消对messagebox行的注释(就在调用while(tasks.Any())之前),应用程序可以正常工作。有人能解释一下为什么应用程序会首先出错,以及如何解决这个问题吗

<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}”已完成,但失败:{