【问题标题】:Threading problem with passing data to a thread from within a DragDrop event从 DragDrop 事件中将数据传递到线程的线程问题
【发布时间】:2011-07-21 22:43:37
【问题描述】:

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

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

如果我通过将 6 个文本文件拖到我的按钮上来执行我的代码(在这个示例中我不得不从中删除很多代码),我将在我的控制台中看到以下内容:

++ 使用以下参数调用测试线程: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

以上输出正确


以下输出不正确,请注意 FilePath 与上述控制台输出中的 CleanFileName 不匹配。

++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test.txt FilePath = c:\test2.txt
++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test1.txt FilePath = c:\test3.txt
++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test3.txt FilePath = c:\test4.txt
++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test4.txt FilePath = c:\test5.txt
++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test5.txt FilePath = c:\test5.txt
++ testthread 线程 - CallingfromPendingUploads == false ButtonName == TestButton CleanFileName == test6.txt FilePath = c:\test5.txt

如您所见,来自线程的 FilePath 与在线程启动之前传递给线程的 FilePath 不匹配。与传递给线程的文件名相比,所有文件路径都关闭。并且有些文件路径是重复的,例如 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);
}

【问题讨论】:

标签: c# multithreading drag-and-drop


【解决方案1】:

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

你需要在循环中声明一个单独的变量,这样每个线程都会得到自己的变量。

例如:

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

由于file 变量现在被限定在循环内,因此每次迭代都会获得一个单独的变量。

【讨论】:

  • 你能给我一个例子,说明如何做到这一点以及代码是什么样的吗?
  • +1,一点代码可以帮助他理解你的意思。关闭相关问题并不明显。
【解决方案2】:

这将解决它:

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

【讨论】:

    【解决方案3】:

    你已经陷入了 lambda 和循环变量的常见陷阱,线程很明显。

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

    这意味着当你这样做时:

    foreach (var outer in collection)
    {
        var state = 42;
        Grok(() => frob(outer, state));
    }
    

    您创建的 lambda 在 outer 上关闭,其在每次循环迭代时都保持相同,即使它的值可能会改变!

    // 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 的情况下,它被声明为在循环内部,因此它在本地在每次循环迭代时被关闭。您需要遵循类似的策略来纠正您对file 的使用:

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

    【讨论】:

      【解决方案4】:

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

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

      编辑 -- 我同意@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);
          }
      }
      

      【讨论】:

      • -1,锁定file 可能修复问题,但出于不同的原因,但使线程一次运行一个,创建一个新的 问题。
      • 让我感到困惑的是,当我从线程输出该变量时,CleanFileName 总是正确的。只有文件输出完全错误的文件路径。
      • .@SLaks 有正确的答案,尽管乍一看为什么他是对的可能令人困惑。
      • @fraXis: CleanFileName 在循环中声明,因此每个线程都有一个单独的变量。
      猜你喜欢
      • 1970-01-01
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 2019-12-27
      • 1970-01-01
      • 1970-01-01
      • 2018-01-30
      • 1970-01-01
      相关资源
      最近更新 更多