没有 ui 冻结的 Task.Factory.StartNew 延迟

Posted

技术标签:

【中文标题】没有 ui 冻结的 Task.Factory.StartNew 延迟【英文标题】:Task.Factory.StartNew delay without ui freez 【发布时间】:2017-03-04 07:45:59 【问题描述】:

当我在UpdateGuItemsAsync 中使用Thread.Sleep 时,程序冻结10 秒,因为线程被阻塞。如果我在“UpdateGuItemsAsync”中使用Task.Delay,代码会立即执行而不会暂停。我希望在不冻结 UI 的情况下在列表更新之前得到延迟。如何在 .net 3.5 中做到这一点?

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(UpdateGuItemsAsync, CancellationToken.None, TaskCreationOptions.None, uiScheduler);


    public void UpdateGuItemsAsync()
    
        System.Threading.Thread.Sleep(10000);
        for (int i = 0; i < 100; i++)
        
            Gu45Document gu45Document = new Gu45Document();
            gu45Document.Form = "EU-45";
            Gu45Documents.Add(gu45Document);
        
    

【问题讨论】:

如果您的 UI 代码是 Wpf,我可以建议您查看 UI 主线程 Dispatcher。 ***.com/questions/11625208/… 在 UI 线程上调用 Task.Factory.StartNewTaskScheduler.FromCurrentSynchronizationContext(); 与直接在 UI 线程上运行代码是一回事。这里不涉及多线程。 【参考方案1】:

编辑

抱歉,我错过了关于 .Net 3.5 导致 Task.Delay 谈话的要点。下面在 .net 3.5 中找到一个示例,该示例在 10 秒后更新表单中的进度条。按下该表单中的按钮时:

using System;
using System.Threading;
using System.Windows.Forms;

  public partial class Form1 : Form
  
    public Form1()
    
      InitializeComponent();
    

    delegate void UpdateGuItemsAsyncDelegate();
    void UpdateGuItemsAsync()
    
      for (int i = 0; i < 100; i++)
      
        progressBar1.PerformStep();
      
    

    private void button1_Click(object sender, EventArgs e)
    
      ThreadPool.QueueUserWorkItem(state =>
      
        Thread.Sleep(10000);

        if (progressBar1.InvokeRequired)
        
          UpdateGuItemsAsyncDelegate del = new UpdateGuItemsAsyncDelegate(UpdateGuItemsAsync);
          this.Invoke(del);
        
        else
        
          UpdateGuItemsAsync();
        

      );
    
  

在 WPF/XAML 中,如果窗口中有进度条和按钮,您几乎可以执行相同的操作:

  <StackPanel>
    <ProgressBar Name="ProgBar" Minimum="0" Maximum="100" Height="20" />
    <Button Name="UpdateCmd" Click="UpdateCmd_Click" Content="Update" />
  </StackPanel>

在代码中:

private void UpdateCmd_Click(object sender, RoutedEventArgs e)

  ThreadPool.QueueUserWorkItem(state =>
  
    Thread.Sleep(10000);

    for (int i = 0; i < 100; i++)
    
      Dispatcher.Invoke(new Action(() =>
      
        ProgBar.Value++;
      ));

      Thread.Sleep(50);
    
  );

【讨论】:

你是对的,它给出了我想要的结果,但在 .net 4.5 中。我需要 .net 3.5 的解决方案。【参考方案2】:

在您的情况下,我会使用System.Windows.Threading.DispatcherTimer 并让它在 10 秒后运行一个事件。这适用于 WPF 和 WinForms,但是如果您的项目是 winforms,则需要在项目引用中添加对 WindowsBase 的引用。

private void UpdateGuItemsAsync()

    //This line must be called on the UI thread
    var timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromSeconds(10);
    timer.Tick += OnUpdateGuiItemsTimerTick;
    timer.Start();


private void OnUpdateGuiItemsTimerTick(object sender, EventArgs e)

    //Stop the timer.
    var timer = sender as DispatcherTimer;
    timer.Stop();

    //Run the code you where waiting for.
    for (int i = 0; i < 100; i++)
    
        Gu45Document gu45Document = new Gu45Document();
        gu45Document.Form = "EU-45";
        Gu45Documents.Add(gu45Document);
    

以下是您在 .NET 3.5 中运行并运行的独立程序,以查看它是否有效。您需要做的就是创建一个新的 Windows 窗体项目,添加对 WindowsBase 的引用并使用以下代码

using System;
using System.Windows.Forms;
using System.Windows.Threading;

namespace WindowsFormsApplication1

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
            this.Load += Form1_Load;
        

        private void Form1_Load(object sender, EventArgs e)
        
            UpdateGuiItems();
        

        private void UpdateGuiItems()
        
            var timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(5);
            timer.Tick += OnUpdateGuiItemsTimerTick;
            timer.Start();
        

        private void OnUpdateGuiItemsTimerTick(object sender, EventArgs e)
        
            var timer = sender as DispatcherTimer;
            timer.Stop();

            MessageBox.Show("Am I on the UI thread: " + !this.InvokeRequired);
        
    

【讨论】:

【参考方案3】:

这应该可以完成工作

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew( () => Thread.Sleep( 10000 ) )
    .ContinueWith(
        t => UpdateGuItemsAsync(),
        CancellationToken.None,
        TaskContinuationOptions.None,
        uiScheduler );

public void UpdateGuItemsAsync()

    // System.Threading.Thread.Sleep(10000);
    for (int i = 0; i < 100; i++)
    
        Gu45Document gu45Document = new Gu45Document();
        gu45Document.Form = "EU-45";
        Gu45Documents.Add(gu45Document);
    

我正在使用 NuGet 包Task Parallel Library for .NET 3.5。

【讨论】:

Task.Delay 在 .NET 3.5 中不存在(实际上整个 System.Threading.Tasks 命名空间在 3.5 中不存在) 好吧,OP 自己提到他用Task.Delay 测试它 - 我认为他已经包含了 TPL NuGet 包 我不知道是否存在任何 NuGet 包。但是,我不相信这里是这种情况。我认为 OP 在 .NET 4.0 或 4.5 中进行了测试,但想知道如何解决 3.5 中的问题 好的,我编辑了我的答案 - 现在使用 .net 3.5 进行测试:o)【参考方案4】:

您可以使用System.Windows.Forms.Timer,它在等待 10 秒时不会阻塞 UI:

 public void UpdateGuItemsAsync()
 
    System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
    timer.Interval = 10000;
    timer.Tick += (sender, args) => 
    
        timer.Stop();
        for (int i = 0; i < 100; i++)
        
            Gu45Document gu45Document = new Gu45Document();
            gu45Document.Form = "EU-45";
            Gu45Documents.Add(gu45Document);
        
    ;
    timer.Start();  
 

【讨论】:

我会将timer.Stop(); 放在for 循环之前,这样如果您的for 循环处理时间过长,您就不会意外排队第二次触发tick 事件。跨度>

以上是关于没有 ui 冻结的 Task.Factory.StartNew 延迟的主要内容,如果未能解决你的问题,请参考以下文章

使用 FirestoreDecoder 解码 Firestore 文档时 UI 冻结

如何在没有冻结UI的情况下使用加载屏幕正确暂停主线程直到满足条件,

如何阻止我的 UI 冻结?

从 iCloud 获取图像将冻结 UI

在 iPhone 7 上的动画块冻结 UI 中调用 layoutIfNeeded

Android RecyclerView 在 notifyDataSetChanged 调用上冻结 UI