【问题标题】:How to make sure a loop within a task has run at least once如何确保任务中的循环至少运行一次
【发布时间】:2016-04-19 18:14:27
【问题描述】:

假设我有一个名为Scheduler 的类,其中包含<UserId, Task>Dictionary,任务不断循环并更新内部字典,其中包含该用户<UserId, Schedule> 的时间表以及来自数据库的信息,即我想要实时更新信息。

我想在SchedulerGetScheduleForUser 上有一个方法,它检查是否有该用户的任务,如果没有,它将创建任务,等待它完成,然后检索Schedules为该用户(延迟加载)。

我的问题是,在任务的第一次迭代之后,我将有一个可用的时间表,我可以检索时间表...没问题,但对于第一次迭代,我需要等到任务完成至少一个检索时间表之前的时间。

我可以开始任务并创建一个 while 循环,直到在第一次循环完成时设置某个标志,但在我看来有更好的方法,它只对第一次迭代有用.之后时间表将始终可用,我不需要该功能。

有没有人有一个干净的方法来完成这个?

【问题讨论】:

  • 您可以使用TaskCompletionSource, SemaphoreSlim, ManualResetEventSlim, Mutex 等同步原语之一。
  • 我正在考虑这样做。问题是我每次都会重置手动事件,即使我真的只需要第一次来确保我拥有所有数据
  • 看看使用LazyConcurrentDictionary:blogs.endjin.com/2015/10/…
  • 如果你的池中有多个线程要运行,这种设计无论如何都不起作用,webgarden 风格......如果你不能控制它,那么你就有问题了。

标签: c# asp.net .net multithreading task


【解决方案1】:

我能想到的最佳解决方案是使用TaskCompletionSource,Eser 在他的评论中提到了这一点。这是一个粗略的代码示例,其中包含大量控制台输出,以便更轻松地了解它在做什么。我还将IDisposable 添加到Scheduler calss 和CancellationTokenSource 的字典中,这样,当您使用完Scheduler 后,它可以停止所有任务。

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

public class Program
{
    // Helper property to simplify console output
    public static string TimeString { get { return DateTime.Now.ToString("mm:ss.fff"); } }

    public static void Main(string[] args)
    {
        using (var scheduler = new Scheduler())
        {
            var userID = "1";

            Console.WriteLine(TimeString + " Main: Getting schedule for first time...");
            var sched1 = scheduler.GetScheduleForUser(userID);
            Console.WriteLine(TimeString + " Main: Got schedule: " + sched1);

            Console.WriteLine(TimeString + " Main: Waiting 2 seconds...");
            System.Threading.Thread.Sleep(2000);

            Console.WriteLine(TimeString + " Main: Getting schedule for second time...");
            var sched2 = scheduler.GetScheduleForUser(userID);
            Console.WriteLine(TimeString + " Main: Got schedule: " + sched2);
        }

        Console.WriteLine();
        Console.WriteLine("Press any key to end . . .");
        Console.ReadKey();
    }
}

public class Scheduler : IDisposable
{
    // Helper property to simplify console output
    public static string TimeString { get { return DateTime.Now.ToString("mm:ss.fff"); } }

    private Dictionary<string, Task> TasksDictionary { get; set; }
    private Dictionary<string, TaskCompletionSource<bool>> TaskCompletionSourcesDictionary { get; set; }
    private Dictionary<string, CancellationTokenSource> CancellationTokenSourcesDictionary { get; set; }
    private Dictionary<string, string> SchedulesDictionary { get; set; }

    public Scheduler()
    {
        TasksDictionary = new Dictionary<string, Task>();
        TaskCompletionSourcesDictionary = new Dictionary<string, TaskCompletionSource<bool>>();
        CancellationTokenSourcesDictionary = new Dictionary<string, CancellationTokenSource>();
        SchedulesDictionary = new Dictionary<string, string>();
    }

    public void Dispose()
    {
        if (TasksDictionary != null)
        {
            if (CancellationTokenSourcesDictionary != null)
            {
                foreach (var tokenSource in CancellationTokenSourcesDictionary.Values)
                    tokenSource.Cancel();

                Task.WaitAll(TasksDictionary.Values.ToArray(), 10000);

                CancellationTokenSourcesDictionary = null;
            }

            TasksDictionary = null;
        }

        CancellationTokenSourcesDictionary = null;
        SchedulesDictionary = null;
    }

    public string GetScheduleForUser(string userID)
    {
        // There's already a schedule, so get it
        if (SchedulesDictionary.ContainsKey(userID))
        {
            Console.WriteLine(TimeString + "     GetSchedule: Already had schedule for user " + userID);
            return SchedulesDictionary[userID];
        }

        // If there's no task yet, start one
        if (!TasksDictionary.ContainsKey(userID))
        {
            Console.WriteLine(TimeString + "     GetSchedule: Starting task for user " + userID);
            var tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            TaskCompletionSourcesDictionary.Add(userID, new TaskCompletionSource<bool>());
            var task = (new TaskFactory()).StartNew(() => GenerateSchedule(userID, token, TaskCompletionSourcesDictionary[userID]), token);
            TasksDictionary.Add(userID, task);
            CancellationTokenSourcesDictionary.Add(userID, tokenSource);
            Console.WriteLine(TimeString + "     GetSchedule: Started task for user " + userID);
        }

        // If there's a task running, wait for it
        Console.WriteLine(TimeString + "     GetSchedule: Waiting for first run to complete for user " + userID);
        var temp = TaskCompletionSourcesDictionary[userID].Task.Result;
        Console.WriteLine(TimeString + "     GetSchedule: First run complete for user " + userID);

        return SchedulesDictionary.ContainsKey(userID) ? SchedulesDictionary[userID] : "null";
    }

    private void GenerateSchedule(string userID, CancellationToken token, TaskCompletionSource<bool> tcs)
    {
        Console.WriteLine(TimeString + "         Task: Starting task for userID " + userID);

        bool firstRun = true;
        while (!token.IsCancellationRequested)
        {
            // Simulate work while building schedule
            if (token.WaitHandle.WaitOne(1000))
                break;

            // Update schedule
            SchedulesDictionary[userID] = "Schedule set at " + DateTime.Now.ToShortTimeString();
            Console.WriteLine(TimeString + "         Task: Updated schedule for userID " + userID);

            // If this was the first run, set the result for the TaskCompletionSource
            if (firstRun)
            {
                tcs.SetResult(true);
                firstRun = false;
            }
        }

        Console.WriteLine(TimeString + "         Task: Ended task for userID " + userID);
    }
}

这里有一个小提琴来展示它:.NET Fiddle

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多