Home > other >  'Cross-thread operation not valid' When copying directory async and changing label.text pr
'Cross-thread operation not valid' When copying directory async and changing label.text pr

Time:04-14

Hi there I am a C# student. On C# Windows Forms.

I am trying to copy directories async when clicking a button and having a label that shows the file being copied.

I get the following error:

System.InvalidOperationException: 'Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on.'

This is my code:

private void CopyDirectoriesAsync(string sourcePath, string destPath)
{
    DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
    if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); }
    foreach (FileInfo file in sourceDirectory.GetFiles())
    {
        label1.Text = file.FullName;
        File.Copy(file.FullName, Path.Combine(destPath, file.Name), true);
    }
    foreach (DirectoryInfo sourceSubDir in sourceDirectory.GetDirectories())
    {
        CopyDirectoriesAsync(sourceSubDir.FullName, Path.Combine(destPath, sourceSubDir.Name));
    }
}

private async void button1_Click(object sender, EventArgs e)
{
    await Task.Run(()=> CopyDirectoriesAsync(@"C:\Users\afernandez.b\1", @"C:\Users\afernandez.b\2"));
    label1.Text = "Finish";
}

Can someone please help me? I've done a lot of research but I have some issue to understand the answers given in other posts.

Thank you very much for your help

CodePudding user response:

You need to invoke the tools which are being modified or used in another thread. Replace your CopyDirectoriesAsync() method with this one and it should work. You should also set the label1.Text = "finish", inside this function only as they should be in the same thread to display values in the correct sequence

private void CopyDirectoriesAsync(string sourcePath, string destPath)
        {
            DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
            if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); }
            foreach (FileInfo file in sourceDirectory.GetFiles())
            {
                label1.Invoke(new Action(() => label1.Text = file.FullName));
                File.Copy(file.FullName, Path.Combine(destPath, file.Name), true);
            }
            foreach (DirectoryInfo sourceSubDir in sourceDirectory.GetDirectories())
            {
                CopyDirectoriesAsync(sourceSubDir.FullName, Path.Combine(destPath, sourceSubDir.Name));
            }
            label1.Invoke(new Action(() => label1.Text = "Finish"));
            
        }

CodePudding user response:

The problem is that the Task.Run method invokes the action on a ThreadPool thread, while all UI components like the Label have a hard requirement to be accessed exclusively on the UI thread. The modern solution to this problem is the Progress<T> class and the IProgress<T> interface. The idea is to pass a Progress<T> instance to the method that runs on the background thread, where T is a type of your choice, so that the method can report progress by invoking the IProgress<T>.Report method. These reports are marshaled to the UI thread by a mechanism called SynchronizationContext, but you don't have to worry about it because this mechanism is installed automatically in WinForms/WPF projects. You just have to make sure that the Progress<T> instance is created on the UI thread.

Example:

private void CopyDirectories(string sourcePath, string destPath,
    IProgress<string> progress, int depth = 0)
{
    DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
    if (!Directory.Exists(destPath)) { Directory.CreateDirectory(destPath); }
    foreach (FileInfo file in sourceDirectory.GetFiles())
    {
        progress.Report(file.FullName);
        File.Copy(file.FullName, Path.Combine(destPath, file.Name), true);
    }
    foreach (DirectoryInfo sourceSubDir in sourceDirectory.GetDirectories())
    {
        CopyDirectories(sourceSubDir.FullName,
            Path.Combine(destPath, sourceSubDir.Name), progress, depth   1);
    }
    if (depth == 0) progress.Report("Finish");
}

private async void button1_Click(object sender, EventArgs e)
{
    var progress = new Progress<string>(message => label1.Text = message);
    await Task.Run(() => CopyDirectories(
        @"C:\Users\afernandez.b\1", @"C:\Users\afernandez.b\2", progress));
}

For more details you can look here: Enabling Progress and Cancellation in Async APIs

  • Related