【问题标题】:Combine backgroundworker and multitread C#结合 backgroundworker 和 multitread C#
【发布时间】:2020-11-20 00:59:48
【问题描述】:

我有一个 C# 程序,它应该运行一个 C++ exe 文件,其中包含 .xlxs 文件形式的几个不同输入。它看起来像这样:

FormfrmRun 打开时,它会创建一个后台工作程序,然后调用myWorker_DoWork

private List<RunSettings> inputfiles = new List<RunSettings>(); //this is just a struct with getter and setters for all of the values that the constructor for frmRun takes

public frmRun(bool SaveCsvFiles, bool DeleteCsvFiles, bool OpenOutputFile, string[] files) //this is called from a main form in the real program
{
    InitializeComponent();
    Show();
    Refresh();
    foreach (string file in files) //for each one of the files in the list
    {
        inputfiles.Add(new RunSettings(file, SaveCsvFiles, DeleteCsvFiles, OpenOutputFile));
    }

    //https://www.codeproject.com/Articles/841751/MultiThreading-Using-a-Background-Worker-Csharp
    //here is the background handler stuff
    myWorker.DoWork += new DoWorkEventHandler(myWorker_DoWork); //Event handler, which will be called when the background worker is instructed to begin its asynchronous work. It is here inside this event where we do our lengthy operations, for example, call a remote server, query a database, process a file... This event is calle
    myWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted); //Event handler, which occurs when the background worker has finished execution has been canceled or has raised an exception.This event is called on the main thread, which means that we can access the user controls from inside this method.
    myWorker.ProgressChanged += new ProgressChangedEventHandler(myWorker_ProgressChanged); //Event handler which occurs when the ReportProgress method of the background worker has been called. We use this method to write the progress to the user interface. This event is called on the main thread, which means that we can access the user controls from inside this method.
    myWorker.WorkerReportsProgress = true; //Needed to instruct the worker that it can report progress to the main thread.
    myWorker.WorkerSupportsCancellation = true; //Needed to instruct the worker that it can be canceled upon the user request.
    myWorker.RunWorkerAsync(files);//Call the background worker
}

然后,在myWorker_DoWork 中,我想处理inputfiles List 中的每个文件所需的所有操作:

protected void myWorker_DoWork(object sender, DoWorkEventArgs e) //starting the program for a list of files
{
    BackgroundWorker sendingWorker = (BackgroundWorker)sender;//Capture the BackgroundWorker that fired the event
    var tasks = new List<Task>();

    for (int i = 0; i < inputfiles.Count; i++) //for each one of the files in the list
    {
        if (!sendingWorker.CancellationPending) //At each iteration of the loop, check if there is a cancellation request pending 
        {
            sendingWorker.ReportProgress(0, "Starting the Program for " + Path.GetFileNameWithoutExtension(inputfiles[i].filename));
            RunMyProgram RunAndSummarize = new RunMyProgram ();
            RunAndSummarize.Progress += ProgressUpdate; //https://stackoverflow.com/questions/14871238/report-progress-backgroundworker-from-different-class-c-sharp
            var task = Task.Run(() => RunAndSummarize.Run(inputfiles[i]));
            tasks.Add(task);

            //RunAndSummarize.Run(inputfiles[i]);
        }
        else
        {
            e.Cancel = true;//If a cancellation request is pending, assign this flag a value of true
            break;// If a cancellation request is pending, break to exit the loop

            //this needs to be reworked a bit if we are gonna use it... We have to call it somewhere and stuff
        }
    }

    Task.WaitAll(tasks.ToArray());

    Close();
}

在我的RunProgram Class 中,除了将.xlsx 文件转换为csv 文件之外,我现在没有做太多事情:

public void Run(RunSettings settings)
{ 
    ExcelFile InputFile = new ExcelFile(); //a class that handels the Excel related stuff in the program
    string fileNameWithoutExt = Path.GetFileNameWithoutExtension(settings.filename);
    string DirectoryofInputFile = Path.GetDirectoryName(settings.filename);
    string DirectoryofOutputFile = DirectoryofInputFile + @"\" + fileNameWithoutExt;

    //the output file is be created in a subfolder with the same name as the input file
    OutputFolder MakeOutputFolderAndCopyInputFile = new OutputFolder();
    MakeOutputFolderAndCopyInputFile.MakeOuputDirectory(DirectoryofOutputFile); //created in the same folder as the input
    MakeOutputFolderAndCopyInputFile.CopyInputFile(settings.filename, DirectoryofOutputFile, " output");

    //saves the excel file to csv
    Progress(0, "Converting the inputfile for " + fileNameWithoutExt + "..."); //https://stackoverflow.com/questions/10775367/cross-thread-operation-not-valid-control-textbox1-accessed-from-a-thread-othe
    InputFile.OpenExcel(DirectoryofInputFile, fileNameWithoutExt + ".xlsx");
    InputFile.SetActiveSheet(0);
    InputFile.SaveAsCsv(DirectoryofOutputFile, fileNameWithoutExt);
    InputFile.Close();
    InputFile = null;
}

我的问题是,当我尝试像上面那样使用multi treading 时,我收到如下错误:

system argument out of range exception: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index.

当我删除myWorker_DoWork 中的多胎面内容并仅将文件逐个转换时,或者当我跳过background worker 并为用户隐藏进度时,代码工作得很好。你知道我做错了什么吗?我不能在 C# 中将 background workermulti thread 结合起来吗?

编辑:进度更新类。

protected void ProgressUpdate(int progress, string text) //tells the user what is happening
{
    base.Invoke((Action)delegate
    {
        string time = DateTime.Now.ToString("HH:mm:ss");

        lblStatus.Text = text + "...";

        string newstatustext = time + ": " + text;
        if (txtProgress.Lines.Length > 0) //dont add a new line for the first line in the textbox, otherwise do
        {
            newstatustext = Environment.NewLine + newstatustext;
        }
        txtProgress.Text += newstatustext;

        txtProgress.Refresh();
        prgProgress.Value += progress / inputfiles.Count; //we give the status for one file but we want the precentage for all of the files
        prgProgress.Refresh();
    });
}

【问题讨论】:

  • 你为什么混合BackgroundWorker和TPL?此外,Task.WaitAll 正在阻止呼叫
  • 我想要一个表单,RunMyProgram 的所有类对象都向其报告,即使我想并行运行每个类对象。当所有的类对象都完成了他们的工作后,我想向用户报告,然后关闭表单。
  • @KGB91 你不需要 BGW,它已经过时并且完全被 Task.Run() 用于后台操作和Progress&lt;T&gt; 类用于进度报告。该类是在 2002 年作为不可见的 Windows 窗体组件添加的。
  • 是的 - UI 只能从 UI 线程修改。 ProgressUpdate 事件必须在 UI 线程上运行。如果 BGW 是在 UI 线程上创建的,那将会发生,Invoke 不是必需的。您的代码虽然非常令人费解并且误用了 BGW。 DoWork 方法实际上并没有做任何事情,它使用 Task.Run 触发其他任务。您可以完全摆脱 BGW,使用 await 等待这些任务完成,然后在 await 之后简单地更新 UI
  • 您可以用简单的await Task.WhenAll() 替换所有这些代码,之后您可以直接更新用户界面。

标签: c# multithreading backgroundworker


【解决方案1】:

您的代码的问题是您在Task.Run 中使用i,但是当Task.Run 中的代码执行时,循环已经完成并且i 的值已经改变.

通过使用变量j 捕获i,您可以避免此问题。

for (int i = 0; i < inputfiles.Count; i++) //for each one of the files in the list
{
    if (!sendingWorker.CancellationPending) //At each iteration of the loop, check if there is a cancellation request pending 
    {
        sendingWorker.ReportProgress(0, "Starting the Program for " + Path.GetFileNameWithoutExtension(inputfiles[i].filename));
        RunMyProgram RunAndSummarize = new RunMyProgram();
        RunAndSummarize.Progress += ProgressUpdate; //https://stackoverflow.com/questions/14871238/report-progress-backgroundworker-from-different-class-c-sharp
        var j = i;
        var task = Task.Run(() => RunAndSummarize.Run(inputfiles[j]));
        tasks.Add(task);

        //RunAndSummarize.Run(inputfiles[i]);
    }
    else
    {
        e.Cancel = true;//If a cancellation request is pending, assign this flag a value of true
        break;// If a cancellation request is pending, break to exit the loop

        //this needs to be reworked a bit if we are gonna use it... We have to call it somewhere and stuff
    }
}

【讨论】:

  • 它似乎有效!在进程结束时出现错误“交叉操作无效:控制 frmRun 从创建它的线程以外的线程访问” - 但它似乎复制并保存了文件。
  • @Sinatr - 我只是想让 OP 先确认一下。
猜你喜欢
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多