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

本文关键字:实例 一个 不受影响 原始 因为 更新 结构 实例方法 ThreadStart | 更新日期: 2023-09-27 18:07:33

我的问题是将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);
        }
    }
}

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

更新

在问题标题更新之后,您看到的问题是结构体在引用时被复制。当将委托分配给线程时,传递的是struct的副本,而这个副本将由线程更新。当你在completeThread中检查时,它是针对未更新的原件的。

使用类而不是结构体。

替代解决方案

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

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

尝试将struct更改为class,您应该会看到问题消失。

这是值(结构)类型的一个细微差别。使用值类型的原因很少——而且在很多情况下,它们会增加开销(而不是像您想象的那样消除开销)。

只需将类型声明更改为class。此外,如果您想要这个函数的超时,为什么不试试下面的方法(您可以完全消除这个类):

    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);
        }
    }