使用任务,“索引超出范围”;和IsCompleted过早返回true

本文关键字:IsCompleted true 返回 范围 任务 索引 | 更新日期: 2023-09-27 18:02:38

我正在尝试测试我对"Windows Store"c#中任务的理解(和学习)。我做了一个多任务应用程序,它包含几个进度条和模拟任务,这些任务在不同的时间段内从1数到100。它最终将成为多线程文件解析器的基础。

我有一个任务处理任务"parseFiles",它通过检查计数器循环遍历任务队列列表,直到它们全部完成。

无论如何,当这个程序运行时,我在"parseFiles"任务的任务处理for循环中随机获得索引超出范围变化的第一次机会异常。循环计数器i不知何故被设置为一个可能大于3的值,尽管for循环显然只将i设置为最大2。

发生的另一件奇怪的事情是,parsers[]中的"解析"任务有时会返回IsCompleted的真值,即使它们还没有开始。

你知道是什么导致了这些奇怪的bug吗?我做错什么了吗?

我也很乐意接受任何关于更健壮或更简单的排队和处理解析任务的方法的建议,因为它可能会修复这两个错误。

代码如下:

namespace TaskTest
{
public sealed partial class MainPage : Page
{
    testFile[] fileList;
    public MainPage()
    {
        this.InitializeComponent();
        fileList = new testFile[10];
        for(int j=0; j<10; j++){
            fileList[j] = new testFile();
        }
    }
    private static void parseFile(testFile file, IProgress<int> progress)
    {
        progress.Report(0);
        file.parse(progress);
    }
    public Task parseFiles(IProgress<int>[] progresses) {
        int nParsers = 3;
        int nParsers = 3;
        if (nParsers > fileList.Length)
        {
            nParsers = fileList.Length;
        }
        Task[] parsers = new Task[nParsers];
        Boolean parseCompleted = false;
        Task.Run(() =>
            {
                //Do the parsing
                int fileCounter = -1;
                Boolean startedLast = false;
                Boolean[] finalTasksDone = new Boolean[nParsers];
                while (!parseCompleted)
                {
                    for(int i=0; i<=(nParsers - 1); i++){
                        if (!startedLast && (parsers[i] == null || parsers[i].IsCompleted))
                        {
                            fileCounter++;
                            Debug.WriteLine("Task " + i + " completed. Starting new Task " + i + " to parse file #" + fileCounter);
                            if (fileCounter == fileList.Length - 1) { startedLast = true; }
                            Action<testFile, Progress<int>> parseAction = parseFile;
                            parsers[i] = Task.Factory.StartNew(() => parseFile(fileList[fileCounter], progresses[i]));
                        }
                        else
                        {
                            if (startedLast && parsers[i].IsCompleted)
                            {
                                finalTasksDone[i] = true;
                            }
                        }
                    }
                    int finishedCounter = 0;
                    for (int i = 0; i <= (nParsers - 1); i++)
                    {
                        if (finalTasksDone[i])
                        {
                            finishedCounter++;
                        }
                    }
                    if (finishedCounter >= nParsers)
                    {
                        parseCompleted = true;
                    }
                }
            });
        return null;
    }

    public class testFile{
        public async void parse(IProgress<int> progress){
            Random random = new Random();
            int simDelay = 1;
            int simFileLength = random.Next(50, 100);
            Debug.WriteLine("Starting file with length: " + simFileLength);
            for(int j=0; j<simFileLength; j++){
                await Task.Delay(TimeSpan.FromSeconds(simDelay));
                int percentProgress = (int) (((double) j/ (double) simFileLength) * 100);
                progress.Report(percentProgress);
            }
        }
    }
    private void button_Click(object sender, RoutedEventArgs e)
    {
        IProgress<int>[] progresses = new Progress<int>[3];
        progresses[0] = new Progress<int>(ReportProgress1);
        progresses[1] = new Progress<int>(ReportProgress2);
        progresses[2] = new Progress<int>(ReportProgress3);
        parseFiles(progresses);
    }
    private void ReportProgress1(int value)
    {
        pBar1.Value = value;
    }
    private void ReportProgress2(int value)
    {
        pBar2.Value = value;
    }
    private void ReportProgress3(int value)
    {
        pBar3.Value = value;
    }
}

}

这是一个典型运行的日志。根据这一点,大多数任务甚至在IsCompleted返回true之前都没有开始。

Task 0 completed. Starting new Task 0 to parse file #0
Task 1 completed. Starting new Task 1 to parse file #1
Task 2 completed. Starting new Task 2 to parse file #2
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
A first chance exception of type 'System.IndexOutOfRangeException' occurred in  TaskTest.exe
Additional information: Index was outside the bounds of the array.
Task 1 completed. Starting new Task 1 to parse file #3
Task 2 completed. Starting new Task 2 to parse file #4
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
Starting file with length: 73
Starting file with length: 83
Task 2 completed. Starting new Task 2 to parse file #5
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
Task 2 completed. Starting new Task 2 to parse file #6
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
Task 1 completed. Starting new Task 1 to parse file #7
Task 2 completed. Starting new Task 2 to parse file #8
Starting file with length: 78
Task 1 completed. Starting new Task 1 to parse file #9
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
A first chance exception of type 'System.IndexOutOfRangeException' occurred in TaskTest.exe
Additional information: Index was outside the bounds of the array.
The program '[7372] TaskTest.exe' has exited with code 1 (0x1).

使用任务,“索引超出范围”;和IsCompleted过早返回true

我在这段代码中看到的主要问题是parseFiles()在许多情况下会过早退出。假设解析器0已经完成,而解析器1和解析器2还有很长的路要走,当您启动所有文件时,startedLast为真。现在,将执行while (!parseCompleted)循环,在for循环中,将发现解析器0已经完成,并将lastTasksCount增加到1。现在,再次执行while (!parseCompleted)循环,在for循环中,再次发现解析器0已完成,并将lastTasksCount增加到2。这再发生一次,lastTasksCount被增加到3,parseCompleted被设置为true,方法退出,而解析器1和解析器2仍在工作。我想,如果修复了这个bug,其他症状可能会消失,或者如果暴露了另一个bug,其他症状也会改变。

关于您的特定症状,您不需要说明您认为IsComplete()在任务尚未启动时返回true的结果是什么特定症状。在这方面有两件事可能会产生误导。一种是当一些任务仍在进行时从parseFiles退出。另一种情况是,当解析器X还不存在时,就在解析器X槽第一次被填充之前,以"Task X completed"开头的调试语句将在开头打印。生成的调试语句可能会有一点误导。

我真的没有看到i越界的方式,尽管如果文件数量少于解析器的数量,将会有一个空指针访问。例如,如果有一个文件和两个解析器,第一次通过for循环将为文件创建解析器0,starttedlast将设置为true,然后下一次通过for循环将执行测试if (startedLast && parsers[1].IsCompleted),而parsers[1]仍然为null。但是,这不会对所写的常量造成问题,因为您有10个文件,大于您拥有的解析器数量3。

同样,我会修复我在第一段中提到的第一个错误,以及当解析器的数量超过文件数量时可能出现的错误,并看看这是否解决了您的问题。