【问题标题】:Cannot update Silverlight UI from asynchronous method completed callback无法从异步方法完成回调更新 Silverlight UI
【发布时间】:2012-02-17 01:27:36
【问题描述】:

我继承了一些通过 WCF 服务查询数据库的代码,然后在完成后使用回调。我正在尝试向该回调添加一些代码,以在处理数据时更新 UI。我发现在回调期间我无法更新 UI:

client.GetDataAsync();
client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(GetDataCompleted);

void GetDataCompleted(object sender, GetDataCompletedEventArgs e)
{
  // Loop through the data
  // ...
  textBlock1.Text= "test1";
  Dispatcher.BeginInvoke(() => textBlock1.Text= "test2" );
  var thread = new Thread(() =>
  {
     // textBlock1.Text= "test3"; (this throws a cross-thread access exception)
     Dispatcher.BeginInvoke(() =>
     {
       textBlock1.Text= "test4";
     });
  }
  thread.Start();
  // ...
  Debug.WriteLine("done");
}

在(显然)整个回调完成之前,这些东西都不会更新 UI。这篇文章:

What thread calls the completed event handler on silverlight WCF calls?

建议回调在主 UI 线程上运行,因此 BeginInvoke 调用应该是不必要的。即使我在上面的代码中添加了各种延迟,它仍然不起作用。这可能吗?有没有更好的方法来做到这一点?

(这是对此的后续问题:Multiple asynchronous UI updates in Silverlight

【问题讨论】:

  • (有更好的方法吗?)您使用的是什么版本的 Silverlight?新的 Task.Factory.FromAsync 是一种享受。您摆脱了事件处理程序,代码变得更具可读性,并且您可以更好地控制执行的位置/时间。

标签: c# wcf silverlight asynchronous callback


【解决方案1】:

degorolls 建议 TPL 是正确的,您的代码如下所示(除了没有 cmets)(此外,必须在 TPL 中处理异常,因此这可能不值得,但我认为不应该这样做) . 第一种方法将保持不变,是的,在基于事件的异步编程中,线程安全得到了照顾(即:你总是返回到你调用的同一个线程) 我还注意到文本输出都在做 = 而不是 +=,但这可能更多是输入溢出的问题 因此,test1 和 test2 将同时打印出来,但是从 TPL 代码中吐出的所有内容都应该在输入时打印出来。 UI 代码不应该做任何需要太多时间的事情,虽然......只是更新 UI。那么,是否认为这是重构的一个点? 让我知道这是否有帮助,或者我是否错过了您正在寻找的内容。

client.GetDataAsync();
client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(GetDataCompleted);

void GetDataCompleted(object sender, GetDataCompletedEventArgs e)
{
  // Loop through the data
  // ...
  textBlock1.Text= "test1";
  //////Dispatcher should not be needed here as this IS on the main UI thread
  Dispatcher.BeginInvoke(() => textBlock1.Text= "test2" );
  //////Everything that happens here should NOT be on the main UI thread, thus the cross-thread access exception
  //////You can do Dispatcher.CheckAccess to determine if you need to invoke or not
  //////Notice the newCopyOfDataToBeWritten. This is a closure, 
  //////so using the same referenced object will result in errant data as it loops
  //////Also, doing it this way does not guarantee any order that this will be written out
  //////This will utilize the parallel fully, but there are ways to force the order
  var task = Task.Factory.StartNew(()=>
    {
      Dispatcher.BeginInvoke(()=>textBlock1.Text += newCopyOfDataToBeWritten)
    }
  );
  // ...
  ///////I assume this is the end of the loop?
  Debug.WriteLine("done");
}

.... 根据您发布的内容,下面的虚拟代码似乎对我有用

 var outsideThread = new Thread(()=>
 {         
     for(int i = 0; i < 20; i++)
          {
              //This code will show all at once since it is on the main thread, 
              //which is still running
              //If you want this to display one at a time also, then you need
              //to use threads and callbacks like below, also
              Dispatcher.BeginInvoke(()=>{textBlock1.Text += "outer" + i;});
              int newI = i;
              var thread = new Thread(() =>
              {
                  System.Threading.Thread.Sleep(1000 * newI);
                  Dispatcher.BeginInvoke(() =>
                  {
                      //This will display as it comes in

                      textBlock1.Text += "inner" + newI;
                  });
              });
              thread.Start();
          }
});
outsideThread.Start();

【讨论】:

  • 谢谢。我认为这不是 SL4 的一个选项,我知道我被困住了......我对这一点更感兴趣,为什么我的上面的代码不起作用!无论问题是什么,这肯定是 SL 编程的基础。
  • 你是对的,我忘记了只有在 SL5 中才有 TPL 原生。但是,我刚刚运行了一些基本上是你所说的代码,它对我有用......我将在上面更新,以便它显示更清晰
  • 它在主线程中工作,但它在回调(GetDataCompleted)中不起作用......看我原始代码的前两行。
  • 是的,正如我解释的那样。我将更新代码以替代使其全部在幕后运行。
  • 基本上,您必须在 UI 线程之外运行所有较慢的逻辑,并在需要更新时回调。请尝试在 UI 逻辑中仅保留 UI 代码。如果花费的时间太长,那么也许您需要考虑向业务逻辑调用推送更多内容?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-16
  • 2015-07-15
相关资源
最近更新 更多