【问题标题】:A test on threads线程测试
【发布时间】:2021-12-04 20:06:54
【问题描述】:

我编写了一个线程测试应用程序来查看多个并行启动的线程返回的行为。我是异步编程的新手,请您对此代码发表评论吗?

我使用 Parallel 创建了多个 TestThreadClass 实例,以查看异步操作同时完成时发生的情况。线程完成后,我将它们添加到线程安全字典 (ConcurrentDictionary) 中,其中包含随机生成的 guid。

下面是一个随机异步类在 1 毫秒内返回;

public class TestThreadClass
{
    private Task<ThreadResultModel> myTask;
    public readonly int myNumber;
    public readonly string myId;

    public TestThreadClass(int _myNumber)
    {
        myNumber = _myNumber;

        myId = Guid.NewGuid().ToString();
    }

    public async Task<ThreadResultModel> StartOperation()
    {
        myTask = InvokeOperation();

        return await myTask;
    }

    private async Task<ThreadResultModel> InvokeOperation()
    {
        await Task.Delay(TimeSpan.FromMilliseconds(1));

        return new ThreadResultModel(myNumber, myId, "Returned");
    }
}

下面是我使用 WinForms 应用程序的创建者类。我启动并行线程,并在它们完成后将它们填充到 GridView 以比较返回毫秒数;

public partial class Form1 : Form
{
    private const int threadNumber = 100;
    private readonly ConcurrentDictionary<string, ThreadResultModel> startedConcurrentDictionary;
    private readonly ConcurrentDictionary<string, ThreadResultModel> returnedConcurrentDictionary;

    public Form1()
    {
       InitializeComponent();

       startedConcurrentDictionary = new ConcurrentDictionary<string, ThreadResultModel>();
       returnedConcurrentDictionary = new ConcurrentDictionary<string, ThreadResultModel>();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
       FillComboboxes();
    }

    private void FillComboboxes()
    {
        for (int i = 1; i <= threadNumber; i++)
        {
           DdlThreadNumber.Items.Add(i.ToString());
        }
    }

    private void BtnStartThreads_Click(object sender, EventArgs e)
    {
        Parallel.For(0, int.Parse(DdlThreadNumber.Text), StartAThread);
    }

    private void StartAThread(int threadTag)
    {
        TestThreadClass t = new TestThreadClass(threadTag);

        startedConcurrentDictionary.TryAdd(t.myId, new ThreadResultModel(threadTag, t.myId, "Started"));
            
       t.StartOperation().ContinueWith(result =>
       {
           returnedConcurrentDictionary.TryAdd(result.Result.MyId, result.Result);
       });
    }

    private void BtnReport_Click(object sender, EventArgs e)
    {
       foreach (var item in startedConcurrentDictionary)
       {
            GrdThreads.Rows.Add(item.Value.MyNumber, item.Value.MyId, item.Value.EventType, item.Value.Time);
       }

       foreach (var item in returnedConcurrentDictionary)
       {
           GrdThreads.Rows.Add(item.Value.MyNumber, item.Value.MyId, item.Value.EventType, item.Value.Time);
       }
    }

    private void GrdThreads_SelectionChanged(object sender, EventArgs e)
    {
        statusLabel.Text = GrdThreads.SelectedRows.Count.ToString();
    }
}

我只是想知道这种方法是否正确。

【问题讨论】:

  • 请注意Parallel.For is not async friendly。您可以查看here 的替代方案。还将ContinueWithawait is not a good idea 结合起来,因为这两种机制完成了同样的事情。使用其中一种,最好使用await
  • @TheodorZoulias 实际上,在这种情况下,Parallel.For 调用了void 方法,这也不是async,所以这不是一个大问题
  • @karolgro 你是对的,Parallel.For 调用了一个void 方法,但整个代码充分说明了作者的意图,即并行化多个异步操作。不幸的是,他们认为Parallel.For 是解决此问题的正确工具,但事实并非如此。 Parallel.ForEachAsync 是,但尚未发布。

标签: c# multithreading asynchronous


【解决方案1】:

我只是想知道这种方法是否正确。

这个问题的答案取决于您要达到的目标。上面的代码所做的是:

  1. Parallel.For 一次执行多个项目,但不是全部。它在底层调度程序上运行 100 次调用,一次执行的次数取决于 CPU 内核的数量。但它不会为每个调用启动新线程,只是重复使用现有线程
  2. 每个StartAThread 都不会启动一个线程。它们只是在 Parallel.For 调度程序的线程上运行,直到 await。之后,它们从任何线程中消失,经过一段时间的延迟,它们被添加到ThreadPool
  3. ThreadPoolawaits 之后执行工作,负载平衡其线程之间的调用
  4. ContinueWith 方法中的代码也添加到ThreadPool

因此,仅使用 awaitParallel.For,您不会创建 100 个线程。由于创建线程很慢并且每个线程都占用大量内存,所有这些功能都将使用一些内部机制来重用现有线程,因为您不需要超过 CPU 可以处理的线程数

但您仍然创建 100 个并发任务,全部拆分为几个部分(使用 awaitContinueWith),由 ThreadPool 在多个线程上独立运行(通常与 CPU 内核/线程的数量有关)。

如果你真的想创建 100 个线程,你需要使用new Thread(...) 调用

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 2013-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-29
    相关资源
    最近更新 更多