简体   繁体   中英

Wait for child task to really compelete

I have the following code that start as a child task to get all files in given directory and do something on theme and call the event for each file to alert parent task :

internal class FileFinder
{
    private readonly string _fileFormat;

    public delegate void FileFoundDelegate(string filePath);

    public event FileFoundDelegate OnFileFound;

    public FileFinder(string fileFormat)
    {
        _fileFormat = fileFormat;
    }

    public bool Start(CancellationToken cancellationToken, string directory)
    {
        try
        {
            if (OnFileFound == null)
                return false;

            var foundedFiles = new ThreadLocal<IEnumerable<string>>();
            try
            {
                foundedFiles.Value = Directory.EnumerateFiles(directory, _fileFormat, SearchOption.AllDirectories)
                    .AsParallel();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Parallel : " + ex.Message);
            }

            foreach (var file in foundedFiles.Value)
            {
                if (cancellationToken.IsCancellationRequested)
                    return true;

                // Call file found event with normalized file name
                OnFileFound?.Invoke(file);
            }

            return true;
        }
        catch (Exception ex)
        {
            Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                ex.InnerException?.Message ?? ex.Message);
            return false;
     }
   }
}

and call it using a parent task called Scatter, the scatter run 5 seprate task that FileFinder is one of theme :

internal class Scatter
    {
        private readonly CancellationToken _cancellationToken;
        private readonly string _directory;
        private readonly string _fileFormat;

        private FileFinder _emailFinder;

        public Scatter(CancellationToken cancellationToken, string directory, string fileFormat)
        {
            _cancellationToken = cancellationToken;
            _directory = directory;
            _fileFormat = fileFormat;
        }

        public Task Start()
        {
            try
            {
                return Task.Factory.StartNew(StartProc,
                    TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private void StartProc()
        {
            try
            {
                // Find pdf files
                _emailFinder = new FileFinder(_fileFormat);
                _emailFinder.OnFileFound += FileFound;
                Task.Factory.StartNew(() => _emailFinder.Start(_cancellationToken, _directory),
                    TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning);
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name, ex.InnerException.Message);
            }
        }

        private void FileFound(string filePath)
        {
            Debug.WriteLine("File Found");
        }
    }

finally a master task run separate scatter for each directory :

internal class Master
    {
        private readonly CancellationToken _cancellationToken;

        internal delegate void ParseFinish();
        public event ParseFinish OnParseFinish;

        public Master(CancellationToken cancellationToken)
        {
            _cancellationToken = cancellationToken;
        }

        public bool Start(List<string> targetDirectories, string fileFormat)
        {
            try
            {
                Task.Factory.StartNew(() => StartProc(targetDirectories, fileFormat), _cancellationToken);
                return true;
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                    ex.InnerException?.Message ?? ex.Message);
                return false;
            }
        }

        private bool StartProc(List<string> directories, string fileFormat)
        {
            try
            {
                List<Task> targetScatterList = new List<Task>();

                foreach (string dir in directories)
                {
                    var scatter = new Scatter(_cancellationToken,dir, fileFormat);

                    targetScatterList.Add(scatter.Start());
                }

                // Wait for finish all tasks & call parse finish event
                Task.WaitAll(targetScatterList.ToArray());
                OnParseFinish?.Invoke();

                return true;
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                    ex.InnerException?.Message ?? ex.Message);
                return false;
            }
        }
    }

i have master task to wait for all directories's task to finish and don't involving the application main thread .

master task call from main thread like this :

    List<string> directoryList = ListBox1.Items.Cast<string>().ToList();

    // Create cancelation token
    _cancellationTokenSource = new CancellationTokenSource();
    _cancellationToken = _cancellationTokenSource.Token;

    // Start master task that populate new task for each target
    var masterTask= new Master(_cancellationToken);
    masterTask.OnParseFinish += ParseFinish;
    masterTask.Start(directoryList, tbFileFormat.Text);

I have 287,198 PDF files in sample books directors, the FileFound event called random times in different run of project (287170, 287182, 287146 and etc) and do not iterate on all founded items .

in small file list it dose not show big difference

I think the parent task goes to finish and the child immediately goes to kill .

Any idea ?

Thanks .

Your code is one of the most amazing amount of over-coding I've seen.

Here's the same code written using Microsoft's Reactive Framework (NuGet "Rx-Main").

var query =
    from dir in directoryList.ToObservable()
    from file in
            Directory.EnumerateFiles(dir, tbFileFormat.Text, SearchOption.AllDirectories)
    select file;

var subscription = query.Subscribe(file =>
{
    ParseFinish(file);
});

That's it. It's all handled using background threads. It gets rid of all of those classes and just does the job you need.

If you want to cancel half-way through, just do this:

subscription.Dispose();

If you want to know when it is complete, just do this:

var subscription = query.Subscribe(file =>
{
    ParseFinish(file);
}, () =>
{
    /* Handle the query is finished here */
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM