【问题标题】:Task.Factory.StartNew delay without ui freez没有 ui 冻结的 Task.Factory.StartNew 延迟
【发布时间】:2023-03-03 01:03:01
【问题描述】:

当我在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 主线程 Dispatcherstackoverflow.com/questions/11625208/…
  • 在 UI 线程上调用 Task.Factory.StartNewTaskScheduler.FromCurrentSynchronizationContext(); 与直接在 UI 线程上运行代码是一回事。这里不涉及多线程。

标签: c# multithreading


【解决方案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 事件。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 1970-01-01
    • 2015-06-16
    • 2017-02-11
    相关资源
    最近更新 更多