c# - 将(结构的)实例方法传递给 ThreadStart 似乎更新了一个虚假实例,因为原始实例不受影响

标签 c# multithreading methods struct locking

我的问题是将 this.folderFolder 实例方法传递给 ThreadStart 构造函数。我使用 dirAssThread 单步执行它并观察它正确更新实例数据成员并完成,然后我回到

if (dirAssThread.IsAlive) completeThread(-1); //***ie abort

并发现我通过方法传递给 ThreadStart 构造函数的同一个 this 实例的数据成员已奇迹般地将自身重置为 0!

这里是其他函数

using System;
using System.IO;
using System.Threading;

namespace MonitorService
{
    struct myStruct
    {
        long bytesSzMember;
        Thread dirAssThread;
        private Object thisLock;

        private void completeThread(long bytesSzNew)
        {
            lock (thisLock)
            {
                if (bytesSzNew == -1)
                {
                    dirAssThread.Abort();
                    Console.WriteLine("A thread timed out.");
                }
                else
                {
                    bytesSzMember = bytesSzNew;
                    Console.WriteLine("A thread update size.");
                }
            }
        }

        private void folderFolder()
        {
            long bytesSzNew = 0;
            DirectoryInfo di = new DirectoryInfo("C:\\SomeDir");
            DirectoryInfo[] directories = di.GetDirectories("*",SearchOption.AllDirectories);
            FileInfo[] files = di.GetFiles("*",SearchOption.AllDirectories);
            foreach (FileInfo file in files)
            {
                bytesSzNew += file.Length;
            }
            completeThread(bytesSzNew);
        }

        private void updateSize()
        {
            thisLock = new Object();
            dirAssThread = new Thread(new ThreadStart(this.folderFolder));
            dirAssThread.Start();
            Thread.Sleep(5000);
            if (dirAssThread.IsAlive) completeThread(-1);
        }
    }
}

最佳答案

更新

问题标题更新后,您看到的问题是结构是根据引用复制的。将委托(delegate)分配给线程时,您正在传递结构的副本,线程将更新该副本。当您检查 completeThread 时,它是针对尚未更新的原始文件。

使用类而不是结构。

替代解决方案

我建议使用等待句柄而不是 sleep 和线程中止,因为 Thread.Abort 被认为是一种危险的做法,应该避免(在这种情况下很容易)。我提出以下解决方案,它是一个递归版本,不会遵循循环引用(因此实际上不需要中止,如果您不想要超时设施,可以删除代码)。

public class WaitForFileSizes
{
    private readonly object _syncObj = new object();
    private readonly HashSet<string> _seenDirectories = new HashSet<string>();
    private readonly ManualResetEvent _pEvent = new ManualResetEvent(false);
    private long _totalFileSize;
    private Thread _thread;
    private volatile bool _abort;

    private void UpdateSize()
    {
        _thread = new Thread(GetDirectoryFileSize);
        _thread.Start();

        bool timedout = !_pEvent.WaitOne(5000);

        if (timedout)
        {
            _abort = true;
            _pEvent.WaitOne();
            Console.WriteLine("A thread timed out.");
        }
        else
        {
            Console.WriteLine("Total size {0}b.", _totalFileSize);
        }
    }

    private void GetDirectoryFileSize()
    {
        GetDirectoryFileSizesRecursively(new DirectoryInfo("C:\\temp"));

        _pEvent.Set();
    }

    private void GetDirectoryFileSizesRecursively(DirectoryInfo dir)
    {
        Parallel.ForEach(dir.EnumerateFiles(), f =>
        {
            if (_abort)
            {
                _pEvent.Set();
                return;
            }

            Interlocked.Add(ref _totalFileSize, f.Length);
        });

        Parallel.ForEach(dir.EnumerateDirectories(), d =>
        {
            if (!IsSeen(d))
            {
                GetDirectoryFileSizesRecursively(d);
            }
        });
    }

    private bool IsSeen(DirectoryInfo dir)
    {
        lock (_syncObj)
        {
            if (!_seenDirectories.Contains(dir.FullName))
            {
                _seenDirectories.Add(dir.FullName);

                return false;
            }

            return true;
        }
    }
}

更新

因为我们现在有循环引用检测,所以可以删除线程和中止代码,因为如果线程处于无限循环中,以前在那里会中止线程——现在不需要了:

public class WaitForFileSizes
{
    private readonly object _syncObj = new object();
    private readonly HashSet<string> _seenDirectories = new HashSet<string>();
    private long _t;

    public void UpdateSize()
    {
        GetSize(new DirectoryInfo("C:\\temp"));

        Console.WriteLine("Total size {0}b.", _t);
    }

    private void GetSize(DirectoryInfo dir)
    {
        Parallel
        .ForEach(dir.EnumerateFiles(), f => Interlocked.Add(ref _t, f.Length));

        Parallel
        .ForEach(dir.EnumerateDirectories().Where(IsNewDir), GetSize);
    }

    private bool IsNewDir(FileSystemInfo dir)
    {
        lock (_syncObj)
        {
            if (!_seenDirectories.Contains(dir.FullName))
            {
                _seenDirectories.Add(dir.FullName);
                return true;
            }
            return false;
        }
    }
}

关于c# - 将(结构的)实例方法传递给 ThreadStart 似乎更新了一个虚假实例,因为原始实例不受影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7318225/

相关文章:

python - 用更少的参数重写方法 - Python

c# - csproj 文件中的 XML

c# - Oracle 号码映射 (ODP.NET)

c++ - 多线程应用程序中的 SetCurrentDirectory

c# - 如何创建一个仅在调用线程持有锁时才运行的方法?

ios - 在主线程中非法访问托管对象上下文,为什么?

java - 平均/最小/最大方法在带有窗口生成器的 java eclipse 中不起作用

c++ - 应该使用什么优雅的方法回调设计?

c# - 32 位和 64 位 C#.NET 中的多目标

c# - WebDriverBackedSelenium 可以在 C# 中使用吗