【问题标题】:File Access and Parallel.For文件访问和 Parallel.For
【发布时间】:2020-11-01 06:06:47
【问题描述】:

我有一组嵌套方法可以有效地复制文件以进行备份,然后使用“RecordConsolidator”类对该文件进行更改。在事件链中,我得到文件正在使用的随机异常,这对我来说没有意义,除非 Parallel.For 调用中的每一行代码都是异步执行的。这是我当前问题的一个示例(在注释行中):

// this is from a method called CompactFormIDs
try
{
    // will this ever be executed twice on the same object?  Will it be 
    // released before the next line of code after the catch statement?
    File.Copy(dest, source); 
}
catch (Exception e)
{
    weirdExceptions.Add(source); // I keep getting a message that the file already exists
    // even though there is only one copy statement which copies the file above.
}

// creates an undo step in a batch file
Globals.AddCommit(CommitType.RestoreBackup | CommitType.UndoDeleteBackup | CommitType.CommitDeleteBackup, source, dest); 

HashSet<FormID> npcList = new HashSet<FormID>();
uint mask = (uint)masters.Count << 24;
report.BeginAppendProcess();

// the method below also causes an exception.
// It acts as though the file copied is still in use.  No other process accesses the file other than the 
// copy process before this statement.  When not doing Parallel.For this works just fine.
using (RecordConsolidator consolidator = new RecordConsolidator(source, dest, mask, npcList))
{...
}

最终目标是:

  1. 制作将要修改的文件的副本,以便在新版本的文件不能正常工作时恢复它。

  2. 将原始文件的恢复添加到批处理脚本中

  3. 对文件进行更改。

如何使用 Parallel.For 方法在并行进程中执行此操作,而不会遇到所有这些“文件正在使用/存在”问题。这里甚至存在问题的事实是没有意义的,因为单个 Copy 语句会导致多个不应发生的问题,除非在执行其余代码之前副本未完成或以某种方式执行 Parallel.For 两次每个项目。

更新 1:这是包含 Parallel.For 循环的方法:

private void OnFormShown(object sender, EventArgs e)
{
    Mod mod;
    RichTextboxBuilder builder;
    List<Mod> batch = task as List<Mod>;
    Refresh();
    if (batch != null)
    {
        RichTextboxBuilder.BeginConcurrentAppendProcess(this, batch.Count);
        ReportCaption = "Conversion Progress";
        progressBar.Visible = true;
        progressBar.Maximum = batch.Count;
        Parallel.For(0, batch.Count, i =>
        {
            mod = batch[i];
            builder = RichTextboxBuilder.BeginConcurrentAppend(i);
            //builder.TextUpdated += Builder_TextUpdated;
            taskTarget.ConvertToESL(mod, builder, false);
            RichTextboxBuilder.EndConcurrentAppend(i);
        });

        Finalize(false);
    }
    else
    {
        mod = task as Mod;
        ReportTextBuilder = new RichTextboxBuilder(this);
        Finalize(taskTarget.ConvertToESL(mod, ReportTextBuilder));
    }
}

【问题讨论】:

  • 问:这会在同一个对象上执行两次吗?答:我们不知道。那是你来决定的。根据您未显示的代码(分配“dest”和“source”的代码)- 会吗?你告诉我们!
  • 我使用了一个哈希集,如果 Add 方法有效则允许处理,然后在处理时从 hasset 中删除。
  • 没有。只有一个 Parallel.For 调用。我会将它添加到我的代码中。
  • 我刚刚意识到一些事情(从我发布的代码中可以看出)。有时消息循环会收到双重消息。由于 parallel.for 在消息处理程序中执行,也许这就是它发生的原因?
  • 什么是taskTarget 它不是函数本地的

标签: c# system.io.file parallel.for


【解决方案1】:

正如评论中提到的,我使用线程安全的 HashSet

var fileQueue = new HashSet<string>(StringComparer.Ordinal);

您可以使用 Lock() 或通过 ReaderWriterLockSlim 对其进行管理以使其成为线程安全的。

我面临的另一个问题是“我在服务器上并不孤单”,这意味着其他进程在做其他事情,我知道这很令人震惊,但有时我并没有把整个服务器都交给我自己 ;-)

看看 Nuget 包Walter

它有一个扩展方法

TryDiscoverWhoisBlocking(this FileInfo file, out IReadOnlyList<Process> processes)

我可以在这里粘贴代码,但是有很多本地方法,帖子会太长。

使用该方法查看谁阻止了您对文件的访问,它可能是病毒扫描程序,如果是这样并且您收到错误,然后循环一段时间让病毒扫描程序执行它的操作,直到所有句柄都属于文件,您可以继续。

我发现大多数时候“我是问题”和“我是阻止访问的人”,所以我查看了我的代码和图,为什么在实际上有办法时使用这些单行按照我的意愿做事。

下面,我告诉我认为需要的阻塞类型,并且只采用我需要的阻塞级别。在您的情况下,您可以打开共享读写而不阻塞或使用...无论如何选择是您的。

using (var fs = new FileStream(path: file.FullName, access: FileAccess.Write, mode: FileMode.Append, share: FileShare.ReadWrite))
using (var sw = new StreamWriter(fs, encoding: UTF8Encoding.UTF8))
{
    sw.Write(text);
    sw.Flush();
}

我写这篇文章是因为我猜你的代码可以工作并且锁定是由另一个线程或另一个进程引起的,上面的代码会明确地告诉你发生了什么。

【讨论】:

  • 非常感谢您的提示。我很确定这个问题是由于我将 Parallel.For 循环放在消息处理程序中并且它被调用了两次。由于阻止了双重消息呼叫,它已经奏效了。但是,防病毒扫描始终是未来用户计算机上可能出现的问题,因此您的建议也将有助于防止该问题。再次感谢。
  • @primem0ver,很高兴您能够解决问题的症状,现在请确保 for 循环在一个不同的列表中,您一切顺利
【解决方案2】:

Walter 的代码绝对可以帮助其他人解决这个问题。然而,我的问题(以及我描述的伴随其发生的不一致)是 Windows 消息重复被发送到消息循环的结果。如果 OnFormShown 消息出现两次,它将调用消息处理程序两次。此修改解决了这个特定问题:

我添加了一个“已处理”类变量,并将添加到我原始帖子中的处理程序更改为以下内容。 (添加Task.Run是为了解决不同的问题)。

private void OnFormShown(object sender, EventArgs e)
{
    if (!processed)
    {
        processed = true;
        Mod mod;
        RichTextboxBuilder builder;
        List<Mod> batch = task as List<Mod>;
        Refresh();
        if (batch != null)
        {
            RichTextboxBuilder.BeginConcurrentAppendProcess(this, batch.Count);
            ReportCaption = "Conversion Progress";
            progressBar.Visible = true;
            progressBar.Maximum = batch.Count;

            Task.Run(() =>
            {
                Parallel.For(0, batch.Count, i =>
                {
                    mod = batch[i];
                    builder = RichTextboxBuilder.BeginConcurrentAppend(i);
                    //builder.TextUpdated += Builder_TextUpdated;
                    taskTarget.ConvertToESL(mod, builder, false);
                    RichTextboxBuilder.EndConcurrentAppend(i);
                }); Finalize(false);
            });
        }
        else
        {
            mod = task as Mod;
            ReportTextBuilder = new RichTextboxBuilder(this);
            Finalize(taskTarget.ConvertToESL(mod, ReportTextBuilder));
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-12
    • 2021-12-19
    • 2021-12-23
    • 2013-05-19
    • 2022-06-28
    • 2021-10-23
    • 2018-08-09
    • 1970-01-01
    相关资源
    最近更新 更多