C 语言中的线程同步问题#

Problem with thread synchronisation in C#

提问人:Lok 提问时间:10/26/2023 最后编辑:Lok 更新时间:10/27/2023 访问量:103

问:

我正在用 C# 创建一个 Discord 机器人,它显示队列中的用户列表。该列表每 30 秒更新一次。

我有两个线程,主线程和我创建的线程。在主线程中,我绑定了一个按钮按下事件处理程序,该事件处理程序上绑定了异步方法。在我创建的线程中,我绑定了一个异步队列更新方法,该方法的延迟为 30 秒,应每 30 秒执行一次。问题在于两个线程都与同一资源(静态字典)交互,这可能导致不正确的数据更改。

我想使用锁结构,但我认为更新队列的线程必须等待很长时间才能进入锁块,这意味着队列不会每 30 秒更新一次。

请帮我解决这个问题,我是编程新手!

private async void button10_Click(object sender, EventArgs e)
{
  new Thread(async () =>
  {
    while (true)
    {
        await UpdateQueue();
        await Task.Delay(30000);
    }
  }).Start();

  channel = await discord.GetChannelAsync(queueChannelID);

  discord.ComponentInteractionCreated += Discord_ButtonIsPressed;

  await discord.ConnectAsync();

  await Task.Delay(-1);
}

private async Task UpdateQueue()
{
  queue.Add("test", "test"); // this is where new items are added to the queue from the database
  blackList.Add("test", "test"); // this is where new items are added to the blacklist from the database

  DiscordMessage message = await channel.GetMessageAsync(messageID);

  await message.ModifyAsync(queue["test"]); // this is where the queue is sent to the Discord channel
}

private async Task Discord_ButtonIsPressed(DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs args)
{
  if (args.Interaction.Data.CustomId == "queue")
  {
     DiscordMessageBuilder message = new();

     DiscordEmbedBuilder embed = new();
     embed.WithTitle("Queue");
     embed.AddField("firstUser", queue["test"]);

     message.AddEmbed(embed);

     await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder(message));
  }

  // there will be logic to handle the fact that a person has made more than 50 requests in 30 seconds
  blackList.Add("test", args.User.Id.ToString());
}
C# 多线程异 争用条件

评论

4赞 Erdit 10/26/2023
遗憾的是,我们看不到您的 IDE。请提供导致问题的代码,以便我们为您提供帮助。
2赞 Panagiotis Kanavos 10/26/2023
您想要解决的实际问题是什么?解释逻辑,而不是你认为它如何实现。
1赞 Tarazed 10/26/2023
如前所述,我们需要查看您的代码以了解您的方法,但是如果您担心的是锁导致其队列长时间延迟,则应仅在修改共享对象时进行锁定,不要在锁中包含不必要的代码。至于正好 30 秒,听起来好像你的目标是精确计时,不幸的是,这意味着你使用的是错误的编程语言。在 C# 中,由于运行时的原因,定时执行是不可靠的。垃圾回收器可以暂停任何线程。无论您使用哪个计时器,都不能保证它会按时执行。
1赞 Ralf 10/26/2023
没有任何真正可用的内容,我从您的问题中解脱出来,即您可以同时访问字典,并且存在问题。框架中有一个 ConcurrentDictionary 可能会在一定程度上有所帮助。你似乎把字典称为这里的队列,对我来说读起来有问题,但希望你知道你在做什么。
1赞 Theodor Zoulias 10/27/2023
顺便说一句,使用委托初始化 a 不是一个好主意。委托是 ,这是要避免的Threadasyncasync void

答:

1赞 hakim00 10/27/2023 #1

实现将确保一次只有一个线程可以执行关键部分。SemaphoreSlimUpdateQueueDiscord_ButtonIsPressed

// Semaphore with 1 initial permit & max 1 permit    
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1,1); 

private async void button10_Click(object sender, EventArgs e)
{
    new Thread(async () =>
    {
        while (true)
        {
            await semaphoreSlim.WaitAsync(); // wait for permit
            try
            {
                await UpdateQueue();
            }
            finally
            {
                semaphoreSlim.Release(); // release
            }

            await Task.Delay(30000);
        }
    }).Start();

    channel = await discord.GetChannelAsync(queueChannelID);

    discord.ComponentInteractionCreated += Discord_ButtonIsPressed;

    await discord.ConnectAsync();

    await Task.Delay(-1);
}

private async Task UpdateQueue()
{
    queue.Add("test", "test");
    blackList.Add("test", "test");

    DiscordMessage message = await channel.GetMessageAsync(messageID);

    await message.ModifyAsync(queue["test"]);
}

private async Task Discord_ButtonIsPressed(DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs args)
{
    await semaphoreSlim.WaitAsync(); // wait for a permit
    try
    {
        if (args.Interaction.Data.CustomId == "queue")
        {
            DiscordMessageBuilder message = new();

            DiscordEmbedBuilder embed = new();
            embed.WithTitle("Queue");
            embed.AddField("firstUser", queue["test"]);

            message.AddEmbed(embed);

            await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder(message));
        }

        blackList.Add("test", args.User.Id.ToString());
    }
    finally
    {
        semaphoreSlim.Release(); // release permit
    }
}

评论

0赞 Lok 10/27/2023
感谢您的回复!您能解释一下这种实现与锁设计有何不同吗?
0赞 hakim00 10/27/2023
Lock只能用于同步代码。例如,您将无法在锁内使用。请参阅 learn.microsoft.com/en-us/dotnet/csharp/language-reference/...awaitDiscord_ButtonisPressed
0赞 hakim00 10/27/2023
如果需要使用异常,则可能需要添加适当的异常处理。
0赞 Lok 10/27/2023
谢谢。还有,会不会有我写过的问题?but I think then the thread updating the queue would have to wait a long time to be let into the lock block, which means that the queue would not be updated every 30 seconds
0赞 hakim00 10/27/2023
您只需要锁定黑名单的资源还是队列?