C# 如何跟踪一个循环中完成了多少异步任务?

C# 如何跟踪一个循环中完成了多少异步任务?,c#,multithreading,c#-4.0,task-parallel-library,task,C#,Multithreading,C# 4.0,Task Parallel Library,Task,我有一些代码,可以循环浏览记录列表,为每个记录启动导出任务,并在每次任务完成时将进度计数器增加1,这样用户就可以知道整个过程有多远 但是,根据循环的计时,我经常看到输出显示一个较高的数字,然后是一个较低的数字 例如,我希望看到如下输出: Exporting A Exporting B Exporting C Exporting D Exporting E Finished 1 / 5 Finished 2 / 5 Finished 3 / 5 Finished 4 / 5 Finished 5

我有一些代码,可以循环浏览记录列表,为每个记录启动导出任务,并在每次任务完成时将进度计数器增加1,这样用户就可以知道整个过程有多远

但是,根据循环的计时,我经常看到输出显示一个较高的数字,然后是一个较低的数字

例如,我希望看到如下输出:

Exporting A Exporting B Exporting C Exporting D Exporting E Finished 1 / 5 Finished 2 / 5 Finished 3 / 5 Finished 4 / 5 Finished 5 / 5
var tasks = new List<Task>();
int counter = 0;

StatusMessage = string.Format("Exporting 0 / {0}", count);

foreach (var value in myValues)
{
    var valueParam = value;

    // Create async task, start it, and store the task in a list
    // so we can wait for all tasks to finish at the end
    tasks.Add(
        Task.Factory.StartNew(() =>
        {
            Debug.WriteLine("Exporting " + valueParam );

            System.Threading.Thread.Sleep(500);
            counter++;
            StatusMessage = string.Format("Exporting {0} / {1}", counter, count);

            Debug.WriteLine("Finished " + counter.ToString());
        })
    );
}

// Begin async task to wait for all tasks to finish and update output
Task.Factory.StartNew(() =>
{
    Task.WaitAll(tasks.ToArray());
    StatusMessage = "Finished";
});
导出 出口B 出口C 出口D 出口电子产品 完成1/5 完成2/5 完成3/5 完成4/5 完成5/5 但我得到的是这样的输出

Exporting A Exporting B Exporting C Exporting D Exporting E Finished 1 / 5 Finished 2 / 5 Finished 5 / 5 Finished 4 / 5 Finished 3 / 5 导出 出口B 出口C 出口D 出口电子产品 完成1/5 完成2/5 完成5/5 完成4/5 完成3/5 我不希望输出是精确的,因为我在更新/使用它时没有锁定值(有时它会输出相同的数字两次,或者跳过一个数字),但是我不希望它返回

我的测试数据集是72个值,相关代码如下所示:

Exporting A Exporting B Exporting C Exporting D Exporting E Finished 1 / 5 Finished 2 / 5 Finished 3 / 5 Finished 4 / 5 Finished 5 / 5
var tasks = new List<Task>();
int counter = 0;

StatusMessage = string.Format("Exporting 0 / {0}", count);

foreach (var value in myValues)
{
    var valueParam = value;

    // Create async task, start it, and store the task in a list
    // so we can wait for all tasks to finish at the end
    tasks.Add(
        Task.Factory.StartNew(() =>
        {
            Debug.WriteLine("Exporting " + valueParam );

            System.Threading.Thread.Sleep(500);
            counter++;
            StatusMessage = string.Format("Exporting {0} / {1}", counter, count);

            Debug.WriteLine("Finished " + counter.ToString());
        })
    );
}

// Begin async task to wait for all tasks to finish and update output
Task.Factory.StartNew(() =>
{
    Task.WaitAll(tasks.ToArray());
    StatusMessage = "Finished";
});
var tasks=newlist();
int计数器=0;
StatusMessage=string.Format(“导出0/{0}”,计数);
foreach(myValues中的var值)
{
var valueParam=价值;
//创建异步任务,启动它,并将任务存储在列表中
//因此,我们可以等待所有任务在最后完成
任务。添加(
Task.Factory.StartNew(()=>
{
Debug.WriteLine(“导出”+valueParam);
系统.线程.线程.睡眠(500);
计数器++;
StatusMessage=string.Format(“导出{0}/{1}”,计数器,计数);
Debug.WriteLine(“Finished”+counter.ToString());
})
);
}
//开始异步任务以等待所有任务完成并更新输出
Task.Factory.StartNew(()=>
{
Task.WaitAll(tasks.ToArray());
StatusMessage=“完成”;
});
输出可以在调试语句和
StatusMessage
输出中向后显示


什么是正确的方法来统计一个循环中完成了多少异步任务,以避免出现此问题?

在本示例中,
计数器
变量表示多个线程之间的共享状态。在共享状态下使用
++
运算符是不安全的,并且会产生错误的结果。它基本上归结为以下说明

  • 将计数器推到堆栈
  • 将1推到堆栈中
  • 在堆栈上添加值
  • 存入柜台
  • 由于多个线程正在执行此语句,因此在完成上述序列的过程中,一个线程可能会中断另一个线程。这将导致计数器中出现不正确的值

    使用以下语句代替
    ++

    Interlocked.Increment(ref counter);
    
    此操作专门用于更新可能在多个线程之间共享的状态。联锁将发生原子,不会受到我概述的比赛条件的影响

    即使在我建议的修复之后,值的实际无序显示也会遇到类似的问题。增量和显示操作不是原子的,因此一个线程可以在增量和显示之间中断另一个线程。如果希望操作不被其他线程中断,则需要使用锁

    object lockTarget = new object();
    int counter = 0; 
    
    ...
    
    lock (lockTarget) {
      counter++;
      StatusMessage = string.Format("Exporting {0} / {1}", counter, count);
      Debug.WriteLine("Finished " + counter.ToString());
    }
    

    请注意,由于
    计数器的增量现在发生在锁内部,因此不再需要使用
    联锁。增量

    会得到混合输出,因为计数器的增量顺序与
    调试的顺序不同。执行WriteLine(…)
    方法

    要获得一致的进度报告,可以在任务中引入报告锁

    tasks.Add(
        Task.Factory.StartNew(() =>
        {
            Debug.WriteLine("Exporting " + valueParam );
    
            System.Threading.Thread.Sleep(500);
            lock(progressReportLock)
            {
               counter++;
               StatusMessage = string.Format("Exporting {0} / {1}", counter, count);
               Debug.WriteLine("Finished " + counter.ToString());
            }
        })
    );
    

    你确定
    异步
    过程会根据我看到的情况以
    升序
    输出吗?我认为不会,除非你在
    列表
    上执行
    排序
    ,因为你在基本相同的时间启动所有任务,它们将以随机顺序完成。即使使用Interlocked.Increment“Finished n/5”也不会是单调的。@DJKRAZE在导出完成之前,
    计数器
    变量实际上不应该增加(出于测试目的,替换为
    线程.Sleep
    )。不同的导出需要不同的时间来完成,这取决于它们包含的数据量,因此我不希望在异步任务开始时预定义值。相反,我尝试使用一个共享变量,该变量在任何导出任务完成时都会增加1。您的代码与示例输出不匹配。我认为,如果您解释一下从哪里获得输出(控制台?或者
    StatusMessage
    是绑定属性?)并在代码中明确说明这一点(在您的示例中,我不认为有任何理由同时使用
    StatucMessage
    Debug.WriteLine()
    。@svick我只需要
    StatusMessage
    更新(是的,是绑定到UI的属性),但在那里添加了调试语句,以便在我开始获得意外结果时进行测试。我添加了“/5”直到我的示例输出的结尾,因为我认为这会更容易理解,但从来没有费心以同样的方式更新调试行。看起来使用Interlocked。Increment对她得到的无序状态报告没有帮助。@alex是正确的,使用
    Interlocked。Increment
    仍然会导致输出顺序错误我怀疑可能是这种情况,谢谢。我使用
    锁运行了一些测试,现在似乎输出正确