代码工作,但似乎同步运行而不是异步

时间:2014-05-07 01:48:45

标签: c# multithreading asynchronous

static void Main(string[] args)
{
    // do async method for each stock in the list
    Task<IEnumerable<string>> symbols = Helper.getStockSymbols("amex", 0);
    List<Task> tasks = new List<Task>();

    try
    {
        for (int i = 0; i < symbols.Result.Count(); i++)
        {
            if (i < symbols.Result.Count())
            {
                string symbol = symbols.Result.ElementAtOrDefault(i);
                Task t = Task.Run(() => getCalculationsDataAsync(symbol, "amex"));
                tasks.Add(t);
                Task e = t.ContinueWith((d) => getThreadStatus(tasks));
            }
        }

        // don't exit until they choose to
        while (args.FirstOrDefault() != "exit")
        {
            // do nothing

        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

public static void getThreadStatus(List<Task> taskList)
{
    int count = 0;

    foreach (Task t in taskList)
    {
        if (t.Status == TaskStatus.Running)
        {
            count += 1;
        }
    }

    Console.WriteLine(count + " threads are running.");
}

public static async Task getCalculationsDataAsync(string symbol, string market)
{
    // do calculation here
}

我在代码中尝试执行的操作是为列表中的每个库存运行一个新任务,并同时运行它们。我有一个4核处理器,我相信这意味着我一次只能运行4个任务。我尝试通过插入您在我的代码中看到的continuewith方法来测试运行的任务数量,该方法将告诉我正在运行的任务数量。当我运行此代码时,它告诉我0个任务正在运行,所以我的问题是:

  1. 如何在同一时间运行这些任务来完成我的目标?
  2. 为什么告诉我0个任务正在运行?我只能假设这是因为当前任务已经完成,如果任务一个接一个地运行,它还没有启动新任务。

3 个答案:

答案 0 :(得分:1)

我不确定为什么你没有看到任何任务在运行。您确定Task.Run()被点击了吗?也就是说i < symbols.Result.Count()满意吗?

无论如何,让我们尝试实现你想要的。首先,不,这是不正确的,因为你的机器有四个物理核心/'线程',你可以使用最多四个Thread s。这些都不是一回事。关于此主题的谷歌将以如此方式提供大量信息。

基本上以你的方式启动线程将在线程池上启动后台线程,并且此线程池可以保存/管理多个线程,请参阅Threading in C# by J. Albahari以获取更多信息。每当你启动一个线程时,花费几百微秒来组织一些新的私有局部变量栈。每个线程也消耗(默认情况下)大约1 MB的内存。线程池通过共享和回收线程来减少这些开销,允许在非常精细的级别上应用多线程而不会降低性能。当利用多核处理器以“分而治之”的方式并行执行计算密集型代码时,这非常有用。

线程池还会限制它将同时运行的工作线程总数。过多的活动线程会对管理负担限制操作系统,并使CPU缓存无效。达到限制后,作业将排队并仅在另一个完成时启动。

好的,现在为您的代码。我们假设我们有一个股票类型列表

List<string> types = new List<string>() { "AMEX", "AMEC", "BP" };

要为每个线程分配多个线程(不使用async / await),您可以执行类似

的操作
foreach (string t in types)
{
    Task.Factory.StartNew(() => DoSomeCalculationForType(t));
}

这将激活三个后台线程池线程并且是非阻塞的,因此这段代码几乎会立即返回给调用者。

如果要设置后期处理,可以通过continuation和continuation chaining来完成。这些都在我上面提供的Albahari链接中描述。

我希望这会有所帮助。

-

修改。发表评论:

  

从.NET Framework版本4开始,进程的线程池的默认大小取决于多个因素,例如虚拟地址空间的大小。进程可以调用GetMaxThreads方法来确定线程数。

然而,还有其他的东西在起作用:线程池不会在所有情况下立即创建新线程。为了应对小任务的爆发,它限制了创建新线程的速度。 IIRC,如果有未完成的任务,它将每0.5秒创建一个线程,直到最大线程数。我不能立即看到这个数字,但它可能会改变。我强烈怀疑这是你所看到的。尝试排队很多项目,然后监视线程数量。

基本上让线程池调度它想要的东西,它的优化器将为你的环境做最好的工作。

答案 1 :(得分:0)

我认为问题在于,任务的状态为Running非常简短,或者可以一起跳过该状态,直接从WaitingForActivation转到RanToCompletion

我稍微修改了你的程序,我可以看到任务的开始和完成,但每当我检查时它们似乎都没有处于Running状态。

static void Main(string[] args)
{
    // do async method for each stock in the list
    Task<IEnumerable<string>> symbols = Task.FromResult(Enumerable.Range(1, 5).Select (e => e.ToString()));

    List<Task> tasks = new List<Task>();

    try
    {
        for (int i = 0; i < symbols.Result.Count(); i++)
        {
             string symbol = symbols.Result.ElementAtOrDefault(i);
             Task t = getCalculationsDataAsync(symbol, "amex", tasks);
             tasks.Add(t);           
        }               

        Console.WriteLine("Tasks Count:"+ tasks.Count());                       
        Task.WaitAll(tasks.ToArray());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

public static void getThreadStatus(List<Task> taskList)
{
    int count = 0;

    foreach (Task t in taskList)
    {
        Console.WriteLine("Status " + t.Status);
        if (t.Status == TaskStatus.Running)
        {
            count += 1;
            Console.WriteLine("A task is running");
        }
    }

    //Console.WriteLine(count + " threads are running.");
}

public static async Task getCalculationsDataAsync(string symbol, string market, List<Task> tasks)
{
    Console.WriteLine("Starting task");
    var delay = new Random((int)DateTime.Now.Ticks).Next(5000);
    Console.WriteLine("Delay:" + delay);
    await Task.Delay(delay);
    Console.WriteLine("Finished task");
    getThreadStatus(tasks);
}

<强>输出

Starting task
Delay:1784
Starting task
Delay:2906
Starting task
Delay:2906
Starting task
Delay:2906
Starting task
Delay:2906
Tasks Count:5
Finished task
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Finished task
Finished task
Finished task
Status RanToCompletion
Status RanToCompletion
Status WaitingForActivation
Status WaitingForActivation
Status RanToCompletion
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Finished task
Status RanToCompletion
Status RanToCompletion
Status RanToCompletion
Status RanToCompletion
Status WaitingForActivation

答案 2 :(得分:0)

正如我在博客中描述的there are two kinds of tasks: Delegate Tasks and Promise Tasks。实际上只有代理任务运行。承诺任务仅完成Task.Run返回的任务是Promise Task,而不是Delegate Task; this is necessary so that Task.Run can understand async code仅在async代码完成时完成。

因此,检查TaskStatus.Running不会按照您想要的方式运行。相反,您可以创建一个计数器:

private static int _count;
static void Main(string[] args)
{
  ...
    for (int i = 0; i < symbols.Result.Count(); i++)
    {
      if (i < symbols.Result.Count())
      {
        string symbol = symbols.Result.ElementAtOrDefault(i);
        Task t = Task.Run(() => getCalculationsDataWithCountAsync(symbol, "amex"));
        tasks.Add(t);
      }
    }
  ...
}

public static async Task getCalculationsDataWithCountAsync(string symbol, string market)
{
  Console.WriteLine(Interlocked.Increment(ref _count) + " threads are running.");
  try
  {
    await getCalculationsDataAsync(symbol, market);
  }
  finally
  {
    Console.WriteLine(Interlocked.Decrement(ref _count) + " threads are running.");
  }
}

请注意,我使用了单独的async&#34;包装器&#34;方法而不是弄乱ContinueWith。使用await代替ContinueWith的代码更能正确处理大量边缘情况,IMO更易于阅读。

另外,请记住,asyncawait会在他们等待的时候释放线程。&#34;因此,您可以同时进行数百个(异步)任务;这并不意味着有那么多线程。