简体   繁体   English

创建多线程应用程序以在 c# 中运行多个查询

[英]Create a multi threaded applications to run multiple queries in c#

I'm trying to build a Windows Forms tool that runs queries asynchronously.我正在尝试构建一个异步运行查询的 Windows 窗体工具。 The app has a datagridview with 30 possible queries to run.该应用程序有一个 datagridview,可以运行 30 个可能的查询。 The user checks the queries he wants to execute, say 10 queries, and hits a button.用户检查他想要执行的查询,比如 10 个查询,然后点击一个按钮。 The app has a variable called maxthreads = 3 (for the sake of discussion) that indicates how many threads can be used to async run the queries.该应用程序有一个名为 maxthreads = 3 的变量(为了讨论),它指示可以使用多少线程来异步运行查询。 The queries run on a production environment and we don't want to overload the system with too many threads running in the same time.查询在生产环境中运行,我们不希望在同时运行太多线程的情况下使系统过载。 Each query runs for an average of 30 sec.每个查询平均运行 30 秒。 (some 5 min., others 2 sec.) In the datagridview there is an image column containing an icon that depicts the status of each query (0- Available to be run, 1-Selected for running, 2- Running, 3- Successfully completed, -1 Error) I need to be able to communicate with the UI every time a query starts and finishes. (大约 5 分钟,其他 2 秒。)在 datagridview 中有一个图像列,其中包含一个描述每个查询状态的图标(0-可运行,1-选择运行,2-正在运行,3-成功已完成,-1 错误)我需要能够在每次查询开始和完成时与 UI 进行通信。 Once a query finishes, the results are being displayed in a datagridview contained in a Tabcontrol (one tab per query)查询完成后,结果将显示在 Tabcontrol 中包含的 datagridview 中(每个查询一个选项卡)

The approach: I was thinking to create a number of maxthread backgroundworkers and let them run the queries.方法:我正在考虑创建许多 maxthread 后台工作人员并让他们运行查询。 As a backgroundworker finishes it communicates to the UI and is assigned to a new query and so on until all queries have been run.后台工作者完成后,它会与 UI 通信并分配给新查询,依此类推,直到所有查询都运行完毕。

I tried using an assignmentWorker that would dispatch the work to the background workers but don't know how to wait for all threads to finish.我尝试使用 assignmentWorker 将工作分派给后台工作人员,但不知道如何等待所有线程完成。 Once a bgw finishes it reports progress on the RunWorkerCompleted event to the assignmentWorker, but that one has already finished. bgw 完成后,它会将 RunWorkerCompleted 事件的进度报告给 assignmentWorker,但该事件已经完成。

In the UI thread I call the assignment worker with all the queries that need to be run:在 UI 线程中,我使用所有需要运行的查询调用分配工作器:

private void btnRunQueries_Click(object sender, EventArgs e)
    {
        if (AnyQueriesSelected())
        {
            tcResult.TabPages.Clear();

            foreach (DataGridViewRow dgr in dgvQueries.Rows)
            {
                if (Convert.ToBoolean(dgr.Cells["chk"].Value))
                {
                    Query q = new Query(dgr.Cells["ID"].Value.ToString(),
                        dgr.Cells["Name"].Value.ToString(),
                        dgr.Cells["FileName"].Value.ToString(),
                        dgr.Cells["ShortDescription"].Value.ToString(),
                        dgr.Cells["LongDescription"].Value.ToString(),
                        dgr.Cells["Level"].Value.ToString(),
                        dgr.Cells["Task"].Value.ToString(),
                        dgr.Cells["Importance"].Value.ToString(),
                        dgr.Cells["SkillSet"].Value.ToString(),
                        false,
                        new Dictionary<string, string>() 
                        { { "#ClntNb#", txtClntNum.Text }, { "#Staff#", "100300" } });

                    qryList.Add(q);
                }
            }
            assignmentWorker.RunWorkerAsync(qryList);
        }
        else
        {
            MessageBox.Show("Please select at least one query.",
                            "Warning",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information);
        }
    }

Here is the AssignmentWorker:这是AssignmentWorker:

private void assignmentWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        foreach (Query q in (List<Query>)e.Argument)
        {
            while (!q.Processed)
            {
                for (int threadNum = 0; threadNum < maxThreads; threadNum++)
                {
                    if (!threadArray[threadNum].IsBusy)
                    {
                        threadArray[threadNum].RunWorkerAsync(q);
                        q.Processed = true;
                        assignmentWorker.ReportProgress(1, q);
                        break;
                    }
                }

                //If all threads are being used, sleep awhile before checking again
                if (!q.Processed)
                {
                    Thread.Sleep(500);
                }
            }
        }
    }

All bgw run the same event:所有 bgw 运行相同的事件:

private void backgroundWorkerFiles_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            Query qry = (Query)e.Argument;

            DataTable dtNew = DataAccess.RunQuery(qry).dtResult;

            if (dsQryResults.Tables.Contains(dtNew.TableName))
            {
                dsQryResults.Tables.Remove(dtNew.TableName);
            }

            dsQryResults.Tables.Add(dtNew);

            e.Result = qry;
        }
        catch (Exception ex)
        {

        }
    }

Once the Query has returned and the DataTable has been added to the dataset:一旦 Query 返回并且 DataTable 已添加到数据集:

private void backgroundWorkerFiles_RunWorkerCompleted(object sender, 
                                                    RunWorkerCompletedEventArgs e)
    {
        try
        {
            if (e.Error != null)
            {
                assignmentWorker.ReportProgress(-1, e.Result);
            }
            else
            {
                assignmentWorker.ReportProgress(2, e.Result);
            }
        }
        catch (Exception ex)
        {
            int o = 0;
        }
    }

The problem I have is that the assignment worker finishes before the bgw finish and the call to assignmentWorker.ReportProgress go to hell (excuse my French).我遇到的问题是分配工作人员在 bgw 完成之前完成,并且对 assignmentWorker.ReportProgress 的调用见鬼去(请原谅我的法语)。 How can I wait for all the launched bgw to finish before finishing the assignment worker?如何在完成分配工作之前等待所有启动的 bgw 完成?

Thank you!谢谢!

As noted in the comment above , you have overcomplicated your design.正如上面的评论中所指出,您的设计过于复杂。 If you have a specific maximum number of tasks (queries) that should be executing concurrently, you can and should simply create that number of workers, and have them consume tasks from your queue (or list) of tasks until that queue is empty.如果您有特定的最大数量的任务(查询)应该同时执行,您可以并且应该简单地创建该数量的工作人员,并让他们从您的任务队列(或列表)中使用任务,直到该队列为空。

Lacking a good Minimal, Complete, and Verifiable code example that concisely and clearly illustrates your specific scenario, it's not feasible to provide code that would directly address your question.缺少一个简洁、清晰地说明您的特定场景的良好的最小、完整和可验证的代码示例,提供可以直接解决您的问题的代码是不可行的。 But, here's an example using a List<T> as your original code does, which will work as I describe above:但是,这是一个使用List<T>作为原始代码的示例,它将按照我上面的描述工作:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TestSO42101517WaitAsyncTasks
{
    class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random();
            int maxTasks = 30,
                maxActive = 3,
                maxDelayMs = 1000,
                currentDelay = -1;
            List<TimeSpan> taskDelays = new List<TimeSpan>(maxTasks);

            for (int i = 0; i < maxTasks; i++)
            {
                taskDelays.Add(TimeSpan.FromMilliseconds(random.Next(maxDelayMs)));
            }

            Task[] tasks = new Task[maxActive];
            object o = new object();

            for (int i = 0; i < maxActive; i++)
            {
                int workerIndex = i;

                tasks[i] = Task.Run(() =>
                {
                    DelayConsumer(ref currentDelay, taskDelays, o, workerIndex);
                });
            }

            Console.WriteLine("Waiting for consumer tasks");

            Task.WaitAll(tasks);

            Console.WriteLine("All consumer tasks completed");
        }

        private static void DelayConsumer(ref int currentDelay, List<TimeSpan> taskDelays, object o, int workerIndex)
        {
            Console.WriteLine($"worker #{workerIndex} starting");

            while (true)
            {
                TimeSpan delay;    
                int delayIndex;

                lock (o)
                {
                    delayIndex = ++currentDelay;
                    if (delayIndex < taskDelays.Count)
                    {
                        delay = taskDelays[delayIndex];
                    }
                    else
                    {
                        Console.WriteLine($"worker #{workerIndex} exiting");
                        return;
                    }
                }

                Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}");
                System.Threading.Thread.Sleep(delay);
            }
        }
    }
}

In your case, each worker would report progress to some global state.在您的情况下,每个工作人员都会向某个全局状态报告进度。 You don't show the ReportProgress handler for your "assignment" worker, so I can't say specifically what this would look like.您没有为“分配”工作人员显示ReportProgress处理程序,因此我无法具体说明这会是什么样子。 But presumably it would involve passing either -1 or 2 to some method that knows what to do with those values (ie what would otherwise have been your ReportProgress handler).但大概它会涉及将-12传递给某个知道如何处理这些值的方法(即,否则将是您的ReportProgress处理程序)。

Note that the code can simplified somewhat, particularly where the individual tasks are consumed, if you use an actual queue data structure for the tasks.请注意,如果您对任务使用实际的队列数据结构,则代码可以稍微简化,尤其是在使用单个任务的情况下。 That approach would look something like this:这种方法看起来像这样:

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace TestSO42101517WaitAsyncTasks
{
    class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random();
            int maxTasks = 30,
                maxActive = 3,
                maxDelayMs = 1000,
                currentDelay = -1;
            ConcurrentQueue<TimeSpan> taskDelays = new ConcurrentQueue<TimeSpan>();

            for (int i = 0; i < maxTasks; i++)
            {
                taskDelays.Enqueue(TimeSpan.FromMilliseconds(random.Next(maxDelayMs)));
            }

            Task[] tasks = new Task[maxActive];

            for (int i = 0; i < maxActive; i++)
            {
                int workerIndex = i;

                tasks[i] = Task.Run(() =>
                {
                    DelayConsumer(ref currentDelay, taskDelays, workerIndex);
                });
            }

            Console.WriteLine("Waiting for consumer tasks");

            Task.WaitAll(tasks);

            Console.WriteLine("All consumer tasks completed");
        }

        private static void DelayConsumer(ref int currentDelayIndex, ConcurrentQueue<TimeSpan> taskDelays, int workerIndex)
        {
            Console.WriteLine($"worker #{workerIndex} starting");

            while (true)
            {
                TimeSpan delay;

                if (!taskDelays.TryDequeue(out delay))
                {
                    Console.WriteLine($"worker #{workerIndex} exiting");
                    return;
                }

                int delayIndex = System.Threading.Interlocked.Increment(ref currentDelayIndex);

                Console.WriteLine($"worker #{workerIndex} sleeping for {delay.TotalMilliseconds} ms, task #{delayIndex}");
                System.Threading.Thread.Sleep(delay);
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM