【问题标题】:Progress message is not in correct order [duplicate]进度消息的顺序不正确[重复]
【发布时间】:2017-11-08 10:35:05
【问题描述】:

玩 Progress 类:

static async Task MyMethodAsync(IProgress<double> progress = null)
{
    int done = 0;
    while (done<100)
    {
        if (progress != null)
            progress.Report(done);
        done++;
    }
}

static async Task CallMyMethodAsync()
{
    var progress = new Progress<double>();
    progress.ProgressChanged += (sender, args) =>
        {
            Console.WriteLine("progr " + args);
        };
    await MyMethodAsync(progress);
}

public static void Main()
{
    CallMyMethodAsync();
    Console.WriteLine("done with caller");
    Console.ReadLine();
}

得到的输出顺序不正确:

done with caller
progr 2
progr 3
progr 4
progr 5
progr 6
progr 7
progr 8
progr 9
progr 10
progr 12
progr 11
progr 0
progr 13
progr 16
progr 17
progr 18
progr 19
progr 20
progr 21
progr 22
progr 23
progr 24
progr 25
progr 26

为什么以及如何实现正确的顺序?

【问题讨论】:

  • 我猜这是一个控制台应用程序?那些没有同步上下文,所以Progress callbacks are serviced on the Thread Pool。所以你不知道它们什么时候会真正被安排,并且多个回调可能同时在飞行中,然后竞争 Console 锁来实际执行它们的写入。
  • 也许 IProgress 不适合在很短的时间内跟踪进度。如果你只是在你的循环中放一个节奏,一切都应该正常工作。在正常用例中,应使用 IProgress 来跟踪函数调用的进度,而不是循环迭代(这要快得多)

标签: c# asynchronous


【解决方案1】:

Progress&lt;T&gt; 类是异步的。它在不同的线程中报告进度,因为它是一个控制台应用程序,正如Damien_The_Unbeliever 解释的那样:

那些没有同步上下文,所以进度回调在线程池上提供服务。所以你不知道它们什么时候会真正被安排,并且多个回调可能同时在飞行中,然后竞争控制台锁来实际执行它们的写入

您可以自己实现IProgress&lt;T&gt;,它是同步的。使用这个:

public class MyProgress<T> : IProgress<T>
{
    public event ProgressChangedEventHandler<T> ProgressChanged;

    public void Report(T value)
    {
        ProgressChanged?.Invoke(this, value);
    }
}

public delegate void ProgressChangedEventHandler<T>(object sender, T progress);

您只需将调用代码更改为:

var progress = new MyProgress<double>();

【讨论】:

    【解决方案2】:

    您所说的“阻止并完成...”是什么意思。

    Progress 类是异步的。它在不同的线程中报告进度,因为它是一个控制台应用程序,正如 Damien_The_Unbeliever 解释的那样:

    这篇文章应该解释为什么你会“失去”一些电话。

    我觉得我对您的应用程序的详细信息还很遥远,但简单的问题是您真的需要为此提供多线程解决方案吗?

    如果您确实需要在其他线程中执行进程并更新 UI,您可以使用下一个逻辑:

    class Class1
    {
        public delegate void ProcessProgressEventHandler(int progress);
        public ProcessProgressEventHandler ProcessProgress;
        public EventHandler ProcessEnd;
        public void StartHeavyProcess()
        {
            System.Threading.Thread vThread = new System.Threading.Thread(HeavyProcess);
            ProcessProgress += OnProgress;
            ProcessEnd += OnProcessEnd;
            vThread.Start();
        }
    
        private void OnProcessEnd(object sender, EventArgs e)
        {
            Console.WriteLine("Process end!");
        }
    
        private void OnProgress(int progress)
        {
            Console.WriteLine("Progress: " + progress.ToString());
        }
    
        public void HeavyProcess()
        {
            int done = 0;
            while (done < 100)
            {
                if (ProcessProgress != null)
                    ProcessProgress(done);
                done++;
            }
            if (ProcessEnd != null)
                ProcessEnd(this, new EventArgs());
        }
    }
    

    附:请注意,如果您的处理程序调用 Windows 控制方法,您必须在 UI 线程中调用方法!

    【讨论】:

      【解决方案3】:

      你得到的结果是因为你使用了启动新线程的任务。

      public static void Main()
      {
          CallMyMethodAsync();
          Console.WriteLine("done with caller");
          Console.ReadLine();
      }
      

      例如第一个命令'CallMyMethodAsync();'只需启动新进程和下一个命令'Console.WriteLine("done with caller");'在第一步开始的过程结束之前执行。 如果您想在第 1 行开始进程结束后从第 2 行执行命令,则为:

      public static void Main()
      {
          CallMyMethodAsync()**.Wait();**
          Console.WriteLine("done with caller");
          Console.ReadLine();
      }
      

      【讨论】:

      • “done with caller”行确实如此,但是您可以看到进度也没有正确增加
      • 这很奇怪,但是调用CallMyMethodAsync().Wait() 仍然不能保证“与调用者完成”将是最后一行。为什么?
      • @vico 我在回答中解释了你。
      • 你解释了If you want to execute command from line 2 after end of process started on line 1 it is: - 使用wait()。我希望流程 1 的结束打印所有流程行。但是在打印所有进度消息之前我得到了done with caller
      • Console.WriteLine(...) 命令在线程 3 中执行(线程 1(等待 2 结束)和线程 2(循环中)除外)。这使得无法保证循环线程将在线程 3 完成时执行下一个命令!!!
      【解决方案4】:

      好的,原因是您使用“事件”,但“事件调度程序”不保证事件将按时间顺序处理。

      我知道的最简单的解决方案是使用委托来通知进度:

      static async Task MyMethodAsync(Action<double> progress = null)
      {
          int done = 0;
          while (done<100)
          {
              if (progress != null)
                  progress.Invoke(done);
              done++;
          }
      }
      

      【讨论】:

      • 如果我使用委托函数CallMyMethodAsync() 块并且done with caller 打印在最底部。为什么?
      猜你喜欢
      • 2020-11-30
      • 2016-02-15
      • 1970-01-01
      • 1970-01-01
      • 2013-07-15
      • 2018-07-30
      • 1970-01-01
      • 2022-08-01
      • 2021-12-15
      相关资源
      最近更新 更多