【问题标题】:c# Thread which creates image getting cancelledc#创建图像的线程被取消
【发布时间】:2013-09-17 08:59:40
【问题描述】:

我有一个附加到 DataGridView 上的 selectionChanged 事件的事件处理程序。在这个处理程序中,我需要创建和加载图像,然后将其显示在图片框中。我遇到的问题是,如果我在行选择之​​间快速跳转,应用程序似乎会挂起,这是我试图避免的问题。

这是我的代码:

    private void loadJobSheet(Job currentJob)
    {

        if (this.jobCardImageThread != null && this.jobCardImageThread.IsAlive)
            this.jobCardImageThread.Abort();

        Image jobCardImage = null;
        this.jobCardImageThread = new Thread(new ThreadStart(
            delegate()
            {
                SavedDocument document = currentJob.SavedDocument;
                DocumentConverter<Bitmap> converter = DocumentConverterFactory<Bitmap>.getDocumentConverterForType(Path.GetExtension(document.Document_Name).Replace('.', ' ').Trim().ToUpper(), typeof(Bitmap));
                jobCardImage = (Image)converter.convertDocument(FileUtils.createTempFile(document.Document_DocumentData.ToArray(), document.Document_Name));
            }
        ));

        jobCardImageThread.Start();
        this.picLoadingJobCard.Visible = true;

        jobCardImageThread.Join();

        if (jobCardImage != null)
        {
            this.picJobCard.Image = jobCardImage;
            this.picLoadingJobCard.Visible = false;
        }
    }

【问题讨论】:

    标签: c# .net multithreading visual-studio-2010 datagridview


    【解决方案1】:

    当你这样做时,你正在等待单独的线程完成

    jobCardImageThread.Join();
    

    这会阻塞 UI 线程,从而挂起应用程序。

    您应该删除 Join() 调用,在 Join() 调用之后创建一个单独的方法,然后从委托中调用该方法。可能使用 Invoke(...) 调用切换回 UI 线程。

    【讨论】:

      【解决方案2】:

      我认为你的问题是 jobCardImageThread.Join();使用此语句,您可以告诉您的线程等待另一个完成。这样你的 UI 就会挂起。

      你为什么不使用后台工作者。例如:

      把它放到你的构造函数中

              this.backgroundWorker = new BackgroundWorker();
              this.backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
              this.backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
              this.backgroundWorker.WorkerSupportsCancellation = true;
      

      并添加以下方法:

          private BackgroundWorker backgroundWorker;
          private AutoResetEvent resetEvent = new AutoResetEvent(false);
          private Thread thread;
      
          private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
          {
              this.picLoadingJobCard.Visible = true;
              Job currentJob = (Job)e.Argument;
              SavedDocument document = currentJob.SavedDocument;
              DocumentConverter<Bitmap> converter = DocumentConverterFactory<Bitmap>.getDocumentConverterForType(Path.GetExtension(document.Document_Name).Replace('.', ' ').Trim().ToUpper(), typeof(Bitmap));
              Image jobCardImage = (Image)converter.convertDocument(FileUtils.createTempFile(document.Document_DocumentData.ToArray(), document.Document_Name));
      
              e.Result = jobCardImage;
          }
      
          private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
          {
              if (e.Error != null)
              {
                  //error-handling
              }
              else if (e.Cancelled)
              {
                  //cancel-handling  
              }
              else
              {
                  Image jobCardImage = e.Result as Image;
                  if (jobCardImage != null)
                      this.picJobCard.Image = jobCardImage;
              }
      
              this.picLoadingJobCard.Visible = false;
              this.resetEvent.Set();
          }
      
      
          private void loadJobSheet(Job currentJob)
          {
              if (this.thread != null)
                  this.thread.Abort();
      
              this.thread = new Thread(new ThreadStart(
              delegate()
              {
                  if (this.backgroundWorker.IsBusy)
                  {
                      this.backgroundWorker.CancelAsync();
                      this.resetEvent.WaitOne();
                  }
                  this.backgroundWorker.RunWorkerAsync(currentJob);
              }));
              this.thread.Start();
          }
      

      【讨论】:

      • 这个概念一般没问题,但是如果在第一个工作完成之前添加一个新工作,它就无法正常工作。 CancelAsync() 除了将CancellationPending 属性设置为true 之外,什么都不做。您应该修改您的代码,为您的方法添加适当的同步,因为如果工作程序仍在运行,RunWorkerAsync 将引发异常。
      • 你说得对。这次应该是正确的(我希望:-)
      • 现在的问题是loadJobSheet 在每次调用时都会创建一个新线程,这意味着您可以有一个竞争条件,即多个线程在WaitOne 上阻塞并且它们同时调用RunWorkerAsync。跨度>
      【解决方案3】:

      如果你创建一个后台Thread并在运行后立即调用Join,你基本上只是浪费时间和内存来创建一个同步方法,因为你当前的线程会阻塞直到后台线程完成。如果当前线程是 UI 线程,这将非常明显。

      Also, using Thread.Abort to kill a thread is not recommended.

      我建议创建一个长期存在的后台线程,该线程大部分时间都会等待来自主线程的signal。这将确保您不会不必要地创建多个线程,以防您收到的请求超出您的工作方法可以处理的数量。

      这是一般的想法:

      // have a long lived and prosperous thread which handles jobs
      private readonly Thread _backgroundWorker;
      
      // you need a way to signal the thread to continue running
      private readonly AutoResetEvent _signalNewTask;
      
      // you need a flag indicating you want to stop (to avoid aborting the thread)
      private volatile bool _keepRunning;
      
      // and you need to pass the current job to that thread
      private volatile Job _currentJob;
      

      循环应该是这样的:

      // this runs on a background thread
      private void WorkerLoop()
      {
          Job lastJob = null; Image lastResult = null;
      
          while (_keepRunning)
          {
              // use an AutoResetEvent for cross-thread signalization
              _signalNewTask.WaitOne();
      
              // make sure the app isn't ending
              if (!_keepRunning)
                  break;
      
              // don't bother if we already processed this job
              if (lastJob == _currentJob)
                  continue;
      
              // capture the job in a local variable
              lastJob = _currentJob;
      
              // long processing
              lastResult = LoadImage(lastJob);
      
              // check if this is still the last requested job
              if (_keepRunning && lastJob == _currentJob)
                  DisplayImage(lastResult);
          }
      }
      

      要安排作业执行,您只需设置字段并发出事件信号:

      private void ScheduleNewJob(Job nextJob)
      {
          // don't waste time if not needed
          if (nextJob == _currentJob)
              return;
      
          _picLoadingJobCard.Visible = true;
          _currentJob = nextJob;
          _signalNewTask.Set();
      }
      

      您还需要将初始化和清理代码添加到您的Form

      public SomeForm()
      {
          InitializeComponent();
      
          _keepRunning = true;
          _signalNewTask = new AutoResetEvent(false);
          _backgroundWorker = new Thread(WorkerLoop);
          _backgroundWorker.IsBackground = true;
          _backgroundWorker.Priority = ThreadPriority.BelowNormal;
          _backgroundWorker.Start();
      }
      
      protected override void OnFormClosed(FormClosedEventArgs e)
      {
          // set the running flag to false and signal the thread 
          // to wake it up
          _keepRunning = false;
          _signalNewTask.Set();
      
          // this will lock very shortly because the background
          // thread breaks when the flag is set
          _backgroundWorker.Join();
      
          base.OnFormClosed(e);
      }
      

      由于DisplayImage(或其他)将从后台线程调用,因此您必须通过调用Invoke 在UI 线程上进行调度:

      private void DisplayImage(Image result)
      {
          if (this.InvokeRequired)
          {
              Invoke(new Action<Image>(DisplayImage), result);
              return;
          }
      
          _picLoadingJobCard.Visible = false;
          _picJobCard.Image = result;
      }
      

      【讨论】:

        猜你喜欢
        • 2011-03-16
        • 2020-06-11
        • 1970-01-01
        • 2011-05-12
        • 2010-12-24
        • 2019-06-24
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        相关资源
        最近更新 更多