C#TPL以并行方式调用任务并异步创建新文件

时间:2015-06-23 23:33:18

标签: c# .net concurrency async-await task-parallel-library

我正在努力学习TPL。我以这样的并行方式写文件:

public async Task SaveToFilesAsync(string path, List<string> list, CancellationToken ct)
{
    int count = 0;
    foreach (var str in list)
    {
        string fullPath = path + @"\" + count.ToString() + "_element.txt";
        using (var sw = File.CreateText(fullPath))
        {
            await sw.WriteLineAsync(str);
        }
        count++;

        Log("Saved in thread: {0} to {1}", 
           Environment.CurrentManagedThreadId,
           fullPath);

        if (ct.IsCancellationRequested)
            ct.ThrowIfCancellationRequested();
    }
}

并称之为:

var tasks = new List<Task>();

try
{
    tasks.Add(SaveToFilesAsync(path, myListOfStrings, cts.Token));
}
catch (Exception ex)
{
    Log("Failed to save: " + ex.Message);
    throw;
}

tasks.Add(MySecondFuncAsync(), cts.Token);
//...
tasks.Add(MyLastFuncAsync(), cts.Token);

try
{
    //Or should I call await Task.WhenAll(tasks) ? What should I call here?
    Task.WaitAll(tasks.ToArray()); 
}
catch (AggregateException ex)
{
    foreach (var v in ex.InnerExceptions)
       Error(ex.Message + " " + v.Message);
}
finally
{
   cts.Dispose();
} 

foreach (task in tasks)
{
// Now, how to print results from the tasks? 
//Considering that all tasks return bool value, 
//I need to do something like this:
if (task.Status != TaskStatus.Faulted)
         Console.Writeline(task.Result);
else
         Log("Error...");
}

我的目标是让所有功能(SaveToFilesAsyncMySecondFuncAsync)以并行方式同时运行,使用计算机上的所有核心并节省时间。但是当我看到SaveToFilesAsync的日志时,我意识到保存到文件总是出现在同一个线程中,而不是并行。我究竟做错了什么?第二个问题:如何从代码末尾的任务列表中的每个任务获取Task.Result?如果第二个函数返回Task(bool),我如何在代码中获得bool值?此外,所有关于我的代码的评论都非常受欢迎,因为我是TPL的新成员。

2 个答案:

答案 0 :(得分:1)

你需要替换从第一个项到最后一个项顺序运行的foreach循环,使用可以配置并行性的Parallel.ForEach()循环,或者Parallel.For(),它可以为你提供索引。目前处理的项目。由于您需要使用计数器作为文件名,因此您需要修改list参数以提供创建列表时填充的文件编号,或使用Parallel.For()提供的索引。另一个选择是有一个很长的变量,你可以在创建文件名后做一个Interlocked.Increment,但我不确定那是不是最佳的,我还没有尝试过。

以下是它的样子。

在try / catch中包装将调用SaveFilesAsync的代码来处理通过CancellationTokenSource取消的操作

hg pushc

然后用该方法进行并行化。

var cts = new CancellationTokenSource();

try
{
    Task.WaitAll(SaveFilesAsync(@"C:\Some\Path", files, cts.Token));
}
catch (Exception)
{
    Debug.Print("SaveFilesAsync Exception");
}
finally
{
    cts.Dispose();
}

代码的其他部分无法更改,只需调整创建文件列表内容的位置。

编辑:围绕调用SaveFileAsync方法的try / catch实际上什么也没做,它全部在SaveFileAsync中处理。

答案 1 :(得分:0)

试试这个:

public async Task SaveToFileAsync(string fullPath, line)
{
    using (var sw = File.CreateText(fullPath))
    {
        await sw.WriteLineAsync(str);
    }

    Log("Saved in thread: {0} to {1}", 
       Environment.CurrentManagedThreadId,
       fullPath);
}

public async Task SaveToFilesAsync(string path, List<string> list)
{
    await Task.WhenAll(
        list
            .Select((line, i) =>
                SaveToFileAsync(
                    string.Format(
                        @"{0}\{1}_element.txt",
                        path,
                        i),
                    line));
}

由于你只为每个文件写了一行而你想要把它全部搞定,我认为它不可取消。