【问题标题】:Multiple Threading多线程
【发布时间】:2026-01-22 07:50:02
【问题描述】:

我正在开发一个应用程序(在 C# 中)。这基本上是一个文件上传/下载应用程序(就像 filezila)。

但有一个特殊功能(文件下载):-

我们可以将文件分成不同的段(最多 100 个)。并且可以通过各种 Cycles 重复这个过程(意味着我们可以将一个文件分成不同的段,并且可以重复这个过程不止一次(通过 Cycles),同时)。

我为此使用线程概念。在应用程序中有一些进度条和标签显示上传/下载状态。

我的问题是:-

如果我为更多数字执行此过程(下载):-

100 个周期和(周期可以超过 100 个)

100 段

意味着我们将一个文件分成 100 个部分(段),并且相同的过程将工作 100 次(周期)。

意味着 100 * 100 =10,000 个文件将通过同一进程同时下载。

但我面临的问题是,如果我为这项任务采取大量数字,那么应该会打开更多线程。

如果有 100 个 TCP 端口/线程同时运行,应用程序将挂起。

为此我尝试了很多东西,但我还没有成功。喜欢 -

异步委托、线程池等

我在更新进度条和标签时遇到了一个主要问题(因为当前的功能正在使用异步委托)。

请给我一些解决这个问题的方法。

【问题讨论】:

    标签: c# .net winforms multithreading


    【解决方案1】:

    因为所有这些线程都想将数据传递给 UI 线程以显示进度,所以 UI 线程正在成为您应用程序的瓶颈。

    您可以添加一个计时器,它会不时让 UI 线程检查作业的进度。

    这样 UI 线程不是由大量线程驱动,而是由时间驱动。

    如果您需要更多详细信息,请发布一些代码。

    【讨论】:

      【解决方案2】:

      你好 Erno 我正在发送一些代码以便更好地理解:-

      这是下载功能:

      Public Sub FileDownload()
      
          txtBytesToDownload.Text = totalBytesToDownload.ToString("N", nfi)  'set textbox with total file size
      
          del = New MyDelegate(AddressOf DownloadDataInRange)
      
          callback = New AsyncCallback(AddressOf CalcCallback)
      
      
          TotalBytesToDownloadForEachCycle = totalBytesToDownload
          downloadedBytes = 0
          dlStopWatch = Stopwatch.StartNew()
      
          Dim flag As Integer = 0
      
          Dim segment As Integer = nuSegment.Value          'total segments
          Dim cyl = cycleNumericUpDown.Value                'total cycles
      
          'ServicePointManager.DefaultConnectionLimit = segment
      
          If UtilityFunctions.CheckRangeSupported(dlUrlHttpTextBox.Text) Then
      
              Dim segmentSize As Integer = 0
              segmentSize = TotalBytesToDownloadForEachCycle / segment
      
              ' For cy As Integer = 0 To (cyl - 1)
      
              For ctr As Integer = 0 To (segment - 1)        'loop for total Segments
      
                  GC.Collect()
                  flag += 1
      
                  Dim fileValues As New FileValues()
      
                  If ctr = 0 Then
                      fileValues.StartPoint = 0               'starting point of each segment (in bytes)
                  Else
                      fileValues.StartPoint = segmentSize * ctr + 1
                  End If
      
                  fileValues.EndPoint = fileValues.StartPoint + segmentSize   'end point of each segment (in bytes)
      
                  If (ctr = (segment - 1)) Then
                      fileValues.EndPoint += TotalBytesToDownloadForEachCycle Mod segment
                  End If
      
                  fileValues.URL = dlUrlHttpTextBox.Text                         'downloading file url
                  Dim str As String = ctr.ToString() + "_" + flag.ToString()
      
                  ' del.BeginInvoke(fileValues, callback, Nothing)
      
      
                  newThread = New System.Threading.Thread(AddressOf DownloadBytes)      'Thread starts here
                  newThread.Priority = ThreadPriority.Highest
                  newThread.IsBackground = True
                  newThread.Start(fileValues)
      
                  'ThreadPool.QueueUserWorkItem(AddressOf DownloadBytes, fileValues)
      
                  fileValues = Nothing
      
              Next
      
              ' ProgressBarTotal.Value += 1
      
              ' Next
      
          Else
              Dim fileValues As New FileValues()
              fileValues.StartPoint = 0
              fileValues.EndPoint = TotalBytesToDownloadForEachCycle
              fileValues.URL = dlUrlHttpTextBox.Text
              Dim newThread As New System.Threading.Thread(AddressOf DownloadBytes)
              newThread.Name = "Thread1"
              newThread.Start(fileValues)
              'ThreadPool.QueueUserWorkItem(AddressOf DownloadBytes, fileValues)
              fileValues = Nothing
      
          End If
      
          Console.WriteLine("Http Downloload End")
          Debug.Print("thread List 1 count =" + _threadsList1.Count.ToString() + ", thread list 2 count =" + _threadsList2.Count.ToString() + ", thread list 3 count =" + _threadsList3.Count.ToString())
      
          'MessageBox.Show("finished - flag=" + flag.ToString())
      
          'After all Cycles Complete 
          'startButton.Enabled = True
          'abortButton.Enabled = False
          'skipButton.Enabled = False
          'DataGridViewDLOrPing.Enabled = True
          'DataGridViewUL.Enabled = True
      
          'protocolComboBox.Enabled = True
          'modelComboBox.Enabled = True
          'testTypeComboBox.Enabled = True
          'measurementComboBox.Enabled = True
          'cycleNumericUpDown.Enabled = True
          'DelayNumericUpDown.Enabled = True
          'InputBoxPrivilege()
      
      End Sub
      

      这是线程将调用以读取字节的函数:

      Public Sub DownloadBytes(ByVal p_fileValues As FileValues)
      
          Dim httpWebRequest As HttpWebRequest
          Dim httpWebResponse As HttpWebResponse
          Dim responseStream As Stream
      
          Dim threadName = threadID
      
          Try
              Console.WriteLine("Start " + Thread.CurrentThread.Name)
              httpWebRequest = DirectCast(WebRequest.Create(p_fileValues.URL), HttpWebRequest)
              'httpWebRequest.ProtocolVersion = HttpVersion.Version11
              httpWebRequest.KeepAlive = False
              httpWebRequest.AddRange(p_fileValues.StartPoint, p_fileValues.EndPoint)
              httpWebRequest.Credentials = CredentialCache.DefaultCredentials
              httpWebRequest.Proxy = WebRequest.DefaultWebProxy
              httpWebResponse = CType(httpWebRequest.GetResponse(), HttpWebResponse)
              responseStream = httpWebResponse.GetResponseStream()
      
              Dim bytesSize As Integer = 0
              ' A buffer for storing and writing the data retrieved from the server
              Dim downBuffer As Byte() = New Byte(2047) {}
              Dim bytesAlreadyDownloaded As Int64 = p_fileValues.StartPoint
      
              ' Loop through the buffer until the buffer is empty
              While (True)
      
                  'end while loop if the Abort button is clicked
                  If (isActionAborted = True) Then
                      Exit While
                  End If
      
                  'currentCycle is > total Cycles ,then end while
                  If (currentCycleDownload > cycleNumericUpDown.Value) Then
                      Exit While
                  End If
      
                  bytesSize = responseStream.Read(downBuffer, 0, downBuffer.Length)
      
                  bytesAlreadyDownloaded += bytesSize
      
                  'speedtimer.Start()
      
                  If (bytesSize <= 0) Then
                      If (downloadedBytes < totalBytesToDownload) Then
                          Me.Invoke(New UploadProgressCallback(AddressOf Me.UpdateDownloadProgress), New Object() {(totalBytesToDownload - downloadedBytes), totalBytesToDownload})
                      End If
      
                      Exit While
                  End If
      
                  ' Invoke the method that updates the form's label and progress bar
                  Me.Invoke(New DownloadProgressCallback(AddressOf Me.UpdateDownloadProgress), New Object() {bytesSize, TotalBytesToDownloadForEachCycle})
      
                  If (bytesAlreadyDownloaded > p_fileValues.EndPoint) Then
                      'Console.WriteLine("Downloading part files Exit " + p_fileValues.StartPoint.ToString() + "," + p_fileValues.EndPoint.ToString())
                      Exit While
                  End If
      
              End While
      
              responseStream.Flush()
              responseStream.Close()
              httpWebResponse.Close()
              responseStream.Dispose()
              responseStream = Nothing
              httpWebResponse = Nothing
              Console.WriteLine("End " + Thread.CurrentThread.Name)
      
              'ProgressDownload.Value = Convert.ToInt32((downloadedBytes * 100) / totalBytesToDownload)
              ' downloadedBytesTextBox.Text = downloadedBytes.ToString("N", nfi)
      
          Catch ex As Exception
      
              Console.WriteLine("Error " + Thread.CurrentThread.Name)
              Console.WriteLine(ex)
      
          Finally
              Console.WriteLine("Finally " + Thread.CurrentThread.Name)
      
              GC.Collect()
      
              ' GC.WaitForPendingFinalizers()
              'newThread.Abort()
      
      
              'Try
              'Thread.CurrentThread.Abort()
      
              'Catch ex1 As Exception
      
              'End Try
          End Try
      
          'Return 1
      
      End Sub
      

      此功能将更新进度条和表单上的其他标签:

      Private Sub UpdateDownloadProgress(ByVal BytesRead As Int64, ByVal TotalBytes As Int64)
          If Not swDL Is Nothing AndAlso swDL.IsRunning Then
              swDL.Stop()
              If swDL.ElapsedMilliseconds > 0 Then
                  resultGrid.Rows.Item(HandoverGridCounter - 1).Cells(4).Value &= "DL: " & swDL.ElapsedMilliseconds & " ms"
              End If
              swDL = Nothing
          End If
      
      
          If (dlCurrentspeed > 0) Then
              'txtCurrentSpeedDL.Text = Math.Round((dlCurrentspeed / 1024), 0) & " KB/s"
          End If
      
      
          downloadedBytes += BytesRead
          If downloadedBytes >= totalBytesToDownload Then
              downloadedBytes = totalBytesToDownload
          End If
      
          ProgressDownload.Value = Convert.ToInt32((downloadedBytes * 100) / TotalBytes)
          downloadedBytesTextBox.Text = downloadedBytes.ToString("N", nfi)  ' & " bytes"
      
          If totalBytesToDownload = 0 Then
              totalBytesToDownload = TotalBytes
              txtBytesToDownload.Text = totalBytesToDownload.ToString("N", nfi)
          End If
      
          If downloadedBytes >= totalBytesToDownload Then
              dlCurrentspeed = 0
              dlStopWatch.Stop()
      
              testEndTickDownload = My.Computer.Clock.TickCount
              testDeltaDownload = (testEndTickDownload - testStartTickDownload) / 1000
              If DLtimedOut = True Then
                  DownloadCompleted(TestStatus.Cancelled.ToString(), "")
              Else
                  DownloadCompleted(TestStatus.Completed.ToString(), "")
              End If
              If currentCycleDownload <= cycleNumericUpDown.Value Then
                  If protocolComboBox.Text = "HTTP" Then
                      StartHttpDownload()
                  Else
                      StartFtpDownload()
                  End If
              End If
          End If
      End Sub
      

      【讨论】:

      • 这不是答案,您应该将其添加到问题中。也就是说,它是一堵代码墙。你能在不丢失关键信息的情况下减少它吗?我希望没有多少读者愿意深入研究这么多代码。
      【解决方案3】:

      要从工作线程(无论是显式创建、排队到线程池的工作项还是来自异步 API 的回调)异步更新 UI,请使用 Control.BeginInvoke

      这不会在等待 UI 线程处理操作时阻塞工作线程。

      但请记住,UI 线程不能假定异步操作完成的特定顺序(以特定顺序开始的两个相同方向的下载不太可能按该顺序完成:太多的外部因素会影响下载速度)。

      最后,大多数BeginABC 方法需要调用相应的EndABC,但(有帮助)Control.BeginInvoke 不需要。

      【讨论】: