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

C# 将(结构的)实例方法传递给ThreadStart似乎会更新虚假实例,因为原始实例不受影响,c#,multithreading,methods,struct,locking,C#,Multithreading,Methods,Struct,Locking,我的问题是将this.folderFolder实例方法传递给ThreadStart。我用dirAssThread一步一步地检查它,看它正确地更新了实例数据成员并完成了更新,然后我返回到 if (dirAssThread.IsAlive) completeThread(-1); //***ie abort 然后发现我用这个方法传递给ThreadStart的同一个实例的数据成员奇迹般地将自己重置为0 以下是其他功能 using System; using System.IO; using Syst

我的问题是将this.folderFolder实例方法传递给ThreadStart。我用dirAssThread一步一步地检查它,看它正确地更新了实例数据成员并完成了更新,然后我返回到

if (dirAssThread.IsAlive) completeThread(-1); //***ie abort
然后发现我用这个方法传递给ThreadStart的同一个实例的数据成员奇迹般地将自己重置为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);
        }
    }
}
更新

在问题标题更新之后,您看到的问题是结构是在引用时复制的。将委托分配给线程时,您正在传递结构的副本,线程将更新该副本。当您办理完入住手续后,请阅读未更新的原件

使用类而不是结构

替代溶液

我建议使用等待句柄而不是休眠和线程中止,因为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;
        }
    }
}
更新

在问题标题更新之后,您看到的问题是结构是在引用时复制的。将委托分配给线程时,您正在传递结构的副本,线程将更新该副本。当您办理完入住手续后,请阅读未更新的原件

使用类而不是结构

替代溶液

我建议使用等待句柄而不是休眠和线程中止,因为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;
        }
    }
}

这里的问题是,您正在将结构的方法传递给ThreadStart构造函数,这会导致它创建结构实例的副本并在副本上调用该方法。您的代码正在运行,但它正在更新副本,而不是原始实例


尝试将结构更改为类,您会看到问题消失。

这里的问题是,您正在将结构的方法传递给ThreadStart构造函数,这会导致它复制结构实例并调用副本上的方法。您的代码正在运行,但它正在更新副本,而不是原始实例


尝试将结构更改为类,您将看到问题消失。

这是值结构类型的细微差别之一。使用值类型的理由很少,而且在很多情况下,它们会增加开销,而不是像您假设的那样消除开销

只需将类型声明更改为类。此外,如果您希望此函数超时,为什么不尝试以下操作?您可以完全消除该类:

    static void Main(string[] args)
    {
        CalculateSize("C:\\", 1000,
            result => Console.WriteLine("Finished: {0} bytes", result),
            (result, ex) => Console.WriteLine("Incomplete results: {0} bytes - {1}", result, ex.Message));
        Console.ReadLine();
    }

    public static void CalculateSize(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
    {
        // Create an invoke a delegate on a separate thread.
        var del = new Action<string, int, Action<long>, Action<long, Exception>>(CalculateSizeImpl);
        del.BeginInvoke(directory, timeout, onSuccess, onFailure, iar =>
            {
                try
                {
                    del.EndInvoke(iar);
                }
                catch (Exception ex)
                {
                    onFailure(0, ex);
                }
            }, null);
    }

    static void CalculateSizeImpl(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
    {
        var completeBy = Environment.TickCount + timeout;
        var size = 0L;
        var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        try
        {
            CalculateSizeRecursive(directory, completeBy, ref size, visited);
        }
        catch (Exception ex)
        {
            // Call the failure callback, but give the
            // value before the timeout to it.
            onFailure(size, ex);
            return;
        }
        // Just return the value.
        onSuccess(size);
    }

    static void CalculateSizeRecursive(string directory, int completeBy, ref long size, HashSet<string> visited)
    {
        foreach (var file in Directory.GetFiles(directory, "*"))
        {
            if (Environment.TickCount > completeBy)
                throw new TimeoutException();
            size += new FileInfo(file).Length;
        }

        // Cannot use SearchOption.All, because you won't get incomplete results -
        // only ever 0 or the actual value.
        foreach (var dir in Directory.GetDirectories(directory, "*"))
        {
            if (Environment.TickCount > completeBy)
                throw new TimeoutException();
            if (visited.Add(dir))
                CalculateSizeRecursive(dir, completeBy, ref size, visited);
        }
    }

这是值结构类型的细微差别之一。使用值类型的理由很少,而且在很多情况下,它们会增加开销,而不是像您假设的那样消除开销

只需将类型声明更改为类。此外,如果您希望此函数超时,为什么不尝试以下操作?您可以完全消除该类:

    static void Main(string[] args)
    {
        CalculateSize("C:\\", 1000,
            result => Console.WriteLine("Finished: {0} bytes", result),
            (result, ex) => Console.WriteLine("Incomplete results: {0} bytes - {1}", result, ex.Message));
        Console.ReadLine();
    }

    public static void CalculateSize(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
    {
        // Create an invoke a delegate on a separate thread.
        var del = new Action<string, int, Action<long>, Action<long, Exception>>(CalculateSizeImpl);
        del.BeginInvoke(directory, timeout, onSuccess, onFailure, iar =>
            {
                try
                {
                    del.EndInvoke(iar);
                }
                catch (Exception ex)
                {
                    onFailure(0, ex);
                }
            }, null);
    }

    static void CalculateSizeImpl(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
    {
        var completeBy = Environment.TickCount + timeout;
        var size = 0L;
        var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        try
        {
            CalculateSizeRecursive(directory, completeBy, ref size, visited);
        }
        catch (Exception ex)
        {
            // Call the failure callback, but give the
            // value before the timeout to it.
            onFailure(size, ex);
            return;
        }
        // Just return the value.
        onSuccess(size);
    }

    static void CalculateSizeRecursive(string directory, int completeBy, ref long size, HashSet<string> visited)
    {
        foreach (var file in Directory.GetFiles(directory, "*"))
        {
            if (Environment.TickCount > completeBy)
                throw new TimeoutException();
            size += new FileInfo(file).Length;
        }

        // Cannot use SearchOption.All, because you won't get incomplete results -
        // only ever 0 or the actual value.
        foreach (var dir in Directory.GetDirectories(directory, "*"))
        {
            if (Environment.TickCount > completeBy)
                throw new TimeoutException();
            if (visited.Add(dir))
                CalculateSizeRecursive(dir, completeBy, ref size, visited);
        }
    }

现在还不清楚你的代码在做什么-你能发布更多的信息吗,或者最好是一个简短但完整的程序吗?@Henrik,不,为了确保完整性,它建议的更新被转发到completeThread,以确保在更新发生之前它不会到期并被终止。@Henrik,我不得不假设我的意思是, bytesSzCode@chibacity,它将完成,5000ms是添加尺寸的足够时间。它将更新大小。如果它没有完成,它将被抢占,bytesSzNew将不会被复制到byteSzCode。@chibacity我知道如果它不能完成,它将通过-1。其效果将是抢占该线程。这是一个看门狗终止符,用于防止在搜索的目录结构中循环引用。您的代码在做什么并不清楚-您可以发布更多信息,或者理想情况下发布一个简短但完整的程序吗?@Henrik,否,为了确保完整性,它建议的更新被转发到completeThread,以确保其不完整
t到期,并在更新发生之前被终止。@Henrik,在3个中,我想我的意思是,bytesSzCode@chibacity,它将完成,5000ms是添加尺寸的足够时间。它将更新大小。如果它没有完成,它将被抢占,bytesSzNew将不会被复制到byteSzCode。@chibacity我知道如果它不能完成,它将通过-1。其效果将是抢占该线程。这是一个看门狗终止符,用于防止搜索目录结构中的循环引用。恐怕我需要某种方法来抢占线程,以便di.GetDirectories调用不会挂起循环sym链接引用。这就是我使用线程的全部目的。@John,你是说如果你有一个循环引用,它在进入foreach循环之前挂起了吗?是的,这在di的合同中。GetDirectories@John您可以考虑使用递归,而不是搜索。然后,您将能够退出循环,如果您愿意,还可以处理循环引用检测。递归是零,我需要更多地接触c和并行。我看到这两种解决方案现在都相当优雅。我将尝试使用类而不是结构。恐怕我需要一些方法来抢占线程,以便di.getDirectory调用不会挂起循环sym链接引用。这就是我使用线程的全部目的。@John,你是说如果你有一个循环引用,它在进入foreach循环之前挂起了吗?是的,这在di的合同中。GetDirectories@John您可以考虑使用递归,而不是搜索。然后,您将能够退出循环,如果您愿意,还可以处理循环引用检测。递归是零,我需要更多地接触c和并行。我看到这两种解决方案现在都相当优雅。我要尝试使用类而不是结构。这让我头脑清醒:这让我头脑清醒: