Передача метода экземпляра (структуры) в ThreadStart, по-видимому, обновляет ложный экземпляр, поскольку на оригинал это не влияет - PullRequest
1 голос
/ 06 сентября 2011

Моя проблема заключается в передаче метода экземпляра this.folderFolder в ThreadStart ctor. Я прошагаю по нему с помощью dirAssThread и наблюдаю, как он корректно обновляет элемент данных экземпляра и завершает его, затем возвращаюсь к

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

и обнаруживает, что элемент данных того же экземпляра this, который я передал с помощью метода в ctor 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);
        }
    }
}

Ответы [ 3 ]

2 голосов
/ 06 сентября 2011

Проблема, с которой вы здесь сталкиваетесь, заключается в том, что вы передаете метод struct в конструктор ThreadStart, в результате чего он создает копию экземпляра struct и вызывает метод для этой копии.Ваш код выполняется, но он обновляет копию, а не исходный экземпляр.

Попробуйте изменить struct на class, и проблема должна исчезнуть.

2 голосов
/ 06 сентября 2011

Обновление

После обновления заголовка вопроса возникает проблема, связанная с копированием структур по ссылке.Вы передаете копию своей структуры при назначении делегата потоку, и именно эта копия будет обновлена ​​потоком.Когда вы делаете чек в completeThread, это против оригинала, который не был обновлен.

Используйте класс вместо struct.

Альтернативное решение

Я бы предложил использовать ручки ожидания вместо снов и прерываний потоков, поскольку 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;
        }
    }
}
0 голосов
/ 06 сентября 2011

Это один из нюансов типов значения (структуры). Существует очень мало причин для использования типа значения - и в самых разных случаях они добавляют накладные расходы (вместо того, чтобы их исключать, как вы могли бы предположить).

Просто измените объявление типа на 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);
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...