C# 从DragDrop事件中向线程传递数据时出现线程问题

C# 从DragDrop事件中向线程传递数据时出现线程问题,c#,multithreading,drag-and-drop,C#,Multithreading,Drag And Drop,我有一个C#应用程序,上面有一个拖放文件的按钮。我可以从我的桌面上取出6个文件,放到按钮上,让它处理这6个文件 但是,当我从DragDrop事件启动线程并将文件路径传递给从DragDrop事件中启动的新线程时,一旦线程接收到FilePath参数,文件路径就不正确 如果我通过将6个文本文件拖到按钮上来执行代码(在本例中,我必须从中删除大量代码),我将在控制台中看到以下内容: ++使用以下参数调用testthread:false、TestButton、test.txt、c:\test.txt ++使

我有一个C#应用程序,上面有一个拖放文件的按钮。我可以从我的桌面上取出6个文件,放到按钮上,让它处理这6个文件

但是,当我从DragDrop事件启动线程并将文件路径传递给从DragDrop事件中启动的新线程时,一旦线程接收到FilePath参数,文件路径就不正确

如果我通过将6个文本文件拖到按钮上来执行代码(在本例中,我必须从中删除大量代码),我将在控制台中看到以下内容:

++使用以下参数调用testthread:false、TestButton、test.txt、c:\test.txt
++使用以下参数调用testthread:false、TestButton、test2.txt、c:\test2.txt
++使用以下参数调用testthread:false、TestButton、test3.txt、c:\test3.txt
++使用以下参数调用testthread:false、TestButton、test4.txt、c:\test4.txt
++使用以下参数调用testthread:false、TestButton、test5.txt、c:\test5.txt
++使用以下参数调用testthread:false、TestButton、test6.txt、c:\test6.txt

上述输出是正确的


以下输出不正确,请注意,文件路径与上述控制台输出中的文件名不匹配。

++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test.txt文件路径=c:\test2.txt
++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test1.txt文件路径=c:\test3.txt
++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test3.txt文件路径=c:\test4.txt
++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test4.txt文件路径=c:\test5.txt
++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test5.txt文件路径=c:\test5.txt
++testthread Thread-CallingfromPendingUploads==false ButtonName==TestButton CleanFileName==test6.txt文件路径=c:\test5.txt

如您所见,来自线程的文件路径与在线程启动前传递给线程的文件路径不匹配。与传递给线程的文件名相比,所有文件路径都处于关闭状态。有些文件路径是重复的,比如text5.txt

我已经为此挣扎了几个小时。有人能告诉我我做错了什么吗?

private void btnClick_DragDrop(object sender, DragEventArgs e)
{
    string[] file = (string[])e.Data.GetData(DataFormats.FileDrop);

    string ButtonName = "TestButton"

    string[] files = new string[10];

    files = (string[])e.Data.GetData(DataFormats.FileDrop);


    foreach (string file in files)
    {
        FileInfo fileInfo = new FileInfo(file);

        Console.WriteLine("++  Filename: " + fileInfo.Name + "   Date of file: " + fileInfo.CreationTime + "   Type of file: " + fileInfo.Extension + "   Size of file: " + fileInfo.Length.ToString());

        string CleanFileName = System.Web.HttpUtility.UrlEncode(fileInfo.Name.ToString());

        //Start  thread
        try
        {
            Console.WriteLine("++ Calling testthread with these params: false, " + ButtonName + "," + CleanFileName + "," + file);

            new Thread(() => testthread(false, ButtonName, CleanFileName, file)).Start();

            Console.WriteLine("++ testthead thread started @ " + DateTime.Now);
         }
         catch (Exception ipwse)
         {
             logger.Debug(ipwse.Message + " " + ipwse.StackTrace);
         }
    }
}

public void testthread(bool CalledfromPendingUploads, string ButtonName, string CleanFileName, string FilePath)
{
    Console.WriteLine("++ testthread Thread - CallingfromPendingUploads == " + CalledfromPendingUploads.ToString() + "  ButtonName == " + ButtonName + "  CleanFileName == " + CleanFileName + "  FilePath = " + FilePath);
}

所有线程共享相同的
文件
变量。
如果其中一个线程仅在UI线程开始下一次迭代后才开始运行,则它将使用
文件
变量的下一个值

您需要在循环中声明一个单独的变量,这样每个线程都将获得自己的变量

例如:

foreach (string dontUse in files)
{
    string file = dontUse;
    ...
}

由于
文件
变量现在在循环中的作用域内,每次迭代都会得到一个单独的变量。

我怀疑
文件
的值可能会在这一行被覆盖:-(至少我在这里是正确的

编辑——我同意@SLaks的答案是正确的,我理解原因。我也理解为什么我的答案不正确。与其删除它,我相信它在演示为什么锁在这种情况下不起作用方面有价值。出于这个原因,我正在做CW

在上面的代码行中进行修改可能不需要锁

我认为你不需要类似的东西:

object key = new object();

private void btnClick_DragDrop(object sender, DragEventArgs e)
{
    // your code ...

    //Start  thread
    try
    {
        Console.WriteLine("++ Calling testthread with these params: false, " + ButtonName + "," + CleanFileName + "," +     file);
        lock (key)
        {
            string[] fileCopy;
            file.CopyTo(fileCopy);

            new Thread(() => testthread(false, ButtonName, CleanFileName, fileCopy)).Start();
        }

        Console.WriteLine("++ testthead thread started @ " + DateTime.Now);
    }
    catch (Exception ipwse)
    {
        logger.Debug(ipwse.Message + " " + ipwse.StackTrace);
    }
}
这将解决它:

string tempFile = file;
new Thread(() => testthread(false, ButtonName, CleanFileName, tempFile)).Start();

您已经陷入了lambdas和循环变量的常见陷阱,线程只是让它变得显而易见

当您创建lambda时,您使用的任何变量都将通过引用而不是您可能假设的值来关闭

这意味着当您执行以下操作时:

foreach (var outer in collection)
{
    var state = 42;
    Grok(() => frob(outer, state));
}
您创建的lambda在
外部
上关闭,其引用在每次循环迭代中保持不变,即使其值可能会更改

// Conceptual look at the previous code
Bar outer; // outside the loop-scope
foreach (outer in collection)
{
    var state = 42; // inside the loop-scope
    Grok(() => frob(outer, state));
}
因此,当您在混合中引入线程时,您已经包含了一个对变量的固定引用,该变量的值正在不同线程上更改。因此,当线程速度减慢时,
file
似乎跳转到最后一个值

CleanFileName
的情况下,它是在循环内声明的,因此它在每次循环迭代时都在本地关闭。您需要遵循类似的策略来更正对
文件的使用:

foreach (var outer in collection)
{
    var inner = outer; // make a closure safe copy of the loop variable
    var state = 42;
    Grok(() => frob(inner, state));
}

您可能应该使用ThreadPool(尽管它不会有帮助)来关闭循环变量的另一种情况:您能给我一个示例,说明如何执行此操作以及代码是什么样子吗?+1,一点代码可以帮助他理解您的意思。与闭包相关的问题并不明显。-1,锁定
文件
可能会出于不同的原因修复问题,但会使线程一次运行一个线程,从而产生新的问题。让我困惑的是,当我从线程输出该变量时,CleanFileName总是正确的。只有文件输出了完全错误的文件路径。@SLaks的答案是正确的,尽管乍一看他的正确性可能令人困惑。@fraXis:
CleanFileName
在循环中声明,因此每个线程都有一个单独的变量。
foreach (var outer in collection)
{
    var inner = outer; // make a closure safe copy of the loop variable
    var state = 42;
    Grok(() => frob(inner, state));
}