Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何使用TPL并行化文件写入?_C#_.net_Multithreading_Task Parallel Library - Fatal编程技术网

C# 如何使用TPL并行化文件写入?

C# 如何使用TPL并行化文件写入?,c#,.net,multithreading,task-parallel-library,C#,.net,Multithreading,Task Parallel Library,我试图将字符串列表保存到多个文件中,每个字符串保存在不同的文件中,并同时保存。我是这样做的: public async Task SaveToFilesAsync(string path, List<string> list, CancellationToken ct) { int count = 0; foreach (var str in list) { string fullPath = path + @"\" + count.ToStr

我试图将字符串列表保存到多个文件中,每个字符串保存在不同的文件中,并同时保存。我是这样做的:

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++;

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

        if (ct.IsCancellationRequested)
            ct.ThrowIfCancellationRequested();
    }
}
try
{
   var savingToFilesTask = SaveToFilesAsync(@"D:\Test", myListOfString, ct);
}
catch(OperationCanceledException)
{
   NLog.Info("Operation has been cancelled by user.");
}

但在日志文件中,我可以清楚地看到保存总是发生在同一个线程id中,所以没有并行性?我做错了什么?如何修复它?我的目标是使用所有的计算机内核尽可能快地保存所有数据

如果这是在并行存储(SSD)上,您可以通过并行化来加快速度。由于没有内置的方法以一定的并行度并行异步循环,我建议PLINQ使用固定的并行度和同步IO<代码>并行。ForEach不能设置为具有固定的DOP(仅限最大DOP)。

如果要进行并行,必须通知.NET进行。 我认为,如果您将代码拆分为一个附加函数,那么实现这一点的最简单方法之一就会变得清晰

public async Task WriteToFile(
        string path,
        string str,
        int count)
{
    var fullPath = string.Format("{0}\\{1}_element.txt", path, count);
    using (var sw = File.CreateText(fullPath))
    {
        await sw.WriteLineAsync(str);
    }

    NLog.Trace("Saved in TaskID: {0} to \"{1}\"", 
       Task.CurrentId,
       fullPath);
}
其思想是将实际的单个IO操作拆分为一个额外的异步函数,并在不等待它们的情况下调用这些函数,而是将它们存储在一个列表中,并在最后等待所有这些任务

我通常不会写C#代码,所以请原谅我可能会犯的任何语法错误:

public async Task SaveToFilesAsync(string path, List<string> list, CancellationToken ct)
{
    int count = 0;
    var writeOperations = new List<Task>(list.Count);
    foreach (var str in list)
    { 
        string fullPath = path + @"\" + count.ToString() + "_element.txt";
        writeOperations.add(SaveToFileAsync(fullPath, str, ct));
        count++;
        ct.ThrowIfCancellationRequested();
    }

    await Task.WhenAll(writeOperations);
}

private async Task SaveToFileAsync(string path, string line, CancellationToken ct)
{
    using (var sw = File.CreateText(path))
    {
        await sw.WriteLineAsync(line);
    }

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

    ct.ThrowIfCancellationRequested();
}
public异步任务SaveToFilesAsync(字符串路径、列表、取消令牌ct)
{
整数计数=0;
var writeOperations=新列表(List.Count);
foreach(列表中的var str)
{ 
字符串fullPath=path++“\”+count.ToString()+“\u element.txt”;
add(SaveToFileAsync(fullPath、str、ct));
计数++;
ct.ThrowIfCancellationRequested();
}
等待任务。WhenAll(写入操作);
}
专用异步任务SaveToFileAsync(字符串路径、字符串行、CancellationToken ct)
{
使用(var sw=File.CreateText(路径))
{
等待sw.WriteLineAsync(行);
}
NLog.Trace(“保存在线程:{0}到{1}中”,
Environment.CurrentManagedThreadId,
全程);
ct.ThrowIfCancellationRequested();
}
这样,IO操作由同一线程一次又一次地触发。这应该很快就能奏效。一旦使用.NET线程池完成IO操作,就会触发延续

我还删除了
if(ct.IsCancellationRequested)
检查,因为这是由
ct.throwifccancellationrequested()完成的无论如何


希望这能给你一个解决这些问题的方法。

本质上,你的问题是
foreach
是同步的。它使用同步的
IEnumerable

为了解决这个问题,首先将循环体封装到异步函数中

public async Task WriteToFile(
        string path,
        string str,
        int count)
{
    var fullPath = string.Format("{0}\\{1}_element.txt", path, count);
    using (var sw = File.CreateText(fullPath))
    {
        await sw.WriteLineAsync(str);
    }

    NLog.Trace("Saved in TaskID: {0} to \"{1}\"", 
       Task.CurrentId,
       fullPath);
}
然后,不要同步循环,而是将字符串序列投影到执行封装循环体的任务序列中。这本身不是一个异步操作,但投影不会阻塞,即没有
wait

然后等待他们按照任务调度器定义的顺序完成所有任务

public async Task SaveToFilesAsync(
        string path,
        IEnumerable<string> list,
        CancellationToken ct)
{
    await Task.WhenAll(list.Select((str, count) => WriteToFile(path, str, count));
}
public异步任务SaveToFilesAsync(
字符串路径,
IEnumerable列表,
取消令牌(ct)
{
wait Task.WhenAll(list.Select((str,count)=>WriteToFile(path,str,count));
}
没有要取消的内容,因此没有必要向下传递取消令牌

我使用了
Select
的索引重载来提供
count


我已将您的日志代码更改为使用当前任务ID,这避免了计划方面的任何混乱。

我已在原始问题中添加了我的答案,是否应将其添加到此处?

编辑:下面是建议的解决方案,它现在并行运行多个save

您需要将foreach循环(从第一项到最后一项依次运行)替换为Parallel.foreach()循环,该循环可以配置为并行

var cts = new CancellationTokenSource();
Task.WaitAll(SaveFilesAsync(@"C:\Some\Path", files, cts.Token));
cts.Dispose();
然后在该方法中进行并行处理

public async Task SaveFilesAsync(string path, List<string> list, CancellationToken token)
{
    int counter = 0;

    var options = new ParallelOptions
                      {
                          CancellationToken = token,
                          MaxDegreeOfParallelism = Environment.ProcessorCount,
                          TaskScheduler = TaskScheduler.Default
                      };

    await Task.Run(
        () =>
            {
                try
                {
                    Parallel.ForEach(
                        list,
                        options,
                        (item, state) =>
                            {
                                // if cancellation is requested, this will throw an OperationCanceledException caught outside the Parallel loop
                                options.CancellationToken.ThrowIfCancellationRequested();

                                // safely increment and get your next file number
                                int index = Interlocked.Increment(ref counter);
                                string fullPath = string.Format(@"{0}\{1}_element.txt", path, index);

                                using (var sw = File.CreateText(fullPath))
                                {
                                    sw.WriteLine(item);
                                }

                                Debug.Print(
                                    "Saved in thread: {0} to {1}",
                                    Thread.CurrentThread.ManagedThreadId,
                                    fullPath);
                            });
                }
                catch (OperationCanceledException)
                {
                    Debug.Print("Operation Canceled");
                }
            });
}
public async Task savefilesaync(字符串路径、列表、取消令牌)
{
int计数器=0;
var options=新的并行选项
{
CancellationToken=令牌,
MaxDegreeOfParallelism=Environment.ProcessorCount,
TaskScheduler=TaskScheduler.Default
};
等待任务。运行(
() =>
{
尝试
{
并行ForEach(
列表
选项,
(项目、状态)=>
{
//如果请求取消,这将抛出在并行循环外部捕获的OperationCanceledException
options.CancellationToken.ThrowIfCancellationRequested();
//安全地增加并获取下一个文件号
int index=联锁增量(参考计数器);
string fullPath=string.Format(@“{0}\{1}{u element.txt”,路径,索引);
使用(var sw=File.CreateText(完整路径))
{
软件写入线(项目);
}
调试。打印(
“保存在以下线程中:{0}到{1}”,
Thread.CurrentThread.ManagedThreadId,
全程);
});
}
捕获(操作取消异常)
{
调试。打印(“操作已取消”);
}
});
}

您的操作是异步运行的,但不是并行运行,这是有区别的。您可以将循环并行化,但我认为这不会产生任何改进-您的操作不受CPU限制,而是受IO限制…您不太可能