4

我有一个按钮,在单击事件时,我会从网络中获取一些信息。

当我得到信息时,我会解析它并将项目添加到 ListBox。一切都很好,但是当我快速双击按钮时,似乎有两个后台工作人员正在运行,完成所有工作后,列表中的项目被复制。

我想这样做,如果您单击按钮并且获取信息的过程正在进行中,则该线程正在停止,并且只有在第一个工作完成后,第二个工作才开始。
是的,我知道 AutoResetEvent,但是当我使用它时,它只帮了我一次,而且再也没有了。我无法实施这种情况,希望您能帮助我!

现在我什至尝试简化但没有成功:(:我添加了一个标志字段(RefreshDialogs)(默认为false),当用户点击按钮时,如果标志为真(这意味着工作正在做),什么都没有做,但是当flag字段设置为false时,一切都很好,我们开始一个新的进程。
当后台工作完成时,我将字段标志更改为false(这意味着用户可以运行一个新的进程)。

    private void Message_Refresh_Click(object sender, EventArgs e)
    {
        if (!RefreshDialogs)
        {
            RefreshDialogs = true;
            if (threadBackgroundDialogs.WorkerSupportsCancellation)
            {
                threadBackgroundDialogs.CancelAsync();
            }
            if (!threadBackgroundDialogs.IsBusy)
            {
                downloadedDialogs = 0;
                threadBackgroundDialogs = new BackgroundWorker();
                threadBackgroundDialogs.WorkerSupportsCancellation = true;
                threadBackgroundDialogs.DoWork += LoadDialogs;
                threadBackgroundDialogs.RunWorkerCompleted += ProcessCompleted;
                threadBackgroundDialogs.RunWorkerAsync();
            }
        }
    }
    void ProcessCompleted(object sender,  RunWorkerCompletedEventArgs e)
    {
        RefreshDialogs = false;
    }
4

6 回答 6

2

所以你想在第一个进程运行时保持第二个进程运行,但它们不应该互相干扰?第一个完成后,第二个继续?

粗鲁方式:While循环:

        if (!RefreshDialogs)
    {
        RefreshDialogs = true;

这变成:

    while(RefreshDialogs)
    {
    }
        RefreshDialogs = true;

将其设置为 false 后,第二个进程将立即跳出。(请注意,这是非常低效的,因为两个进程都将一直运行,我很确定第二个进程会阻塞第一个进程,但现在多任务处理不应该,如果它阻塞使用 Dispatcher.Thread)

优雅的方式:使用信号量

http://msdn.microsoft.com/de-de/library/system.threading.semaphore%28v=vs.80%29.aspx

如果您发现不可能让两个进程同时运行,或者想要另一种方式:添加一个 Array/List/int 并且当第二个进程注意到第一个进程正在运行时,就像您的 bool 一样,增加您的 Added 变量,并在进程结束时,重新启动新进程并减少变量:

    int number;

    if (!RefreshDialogs)
    {
        RefreshDialogs = true;
        your code;
        if(number > 0)
        {
          number--;
          restart process
        }
    }
    else
    {
    number++;
    }

我不得不承认,我最喜欢我的最后一个建议,因为它非常有效。

于 2012-08-27T08:56:22.230 回答
1

让你的线程阻塞。这很容易;

lock(someSharedGlobalObject)
{
  Do Work, Exit early if cancelled
}

这样其他线程将等到第一个线程释放锁。它们永远不会同时执行并默默地等待,直到它们可以继续。

至于其他选择;为什么不在单击时禁用按钮并在后台工作人员完成时重新启用它。唯一的问题是这不允许取消当前线程。用户必须等待它完成。它确实使任何并发性都非常容易消失。

于 2012-08-27T09:07:21.580 回答
1

这种方法怎么样?

创建一个请求队列或计数器,每次单击按钮时都会递增。每次计数 > 0。启动后台工作程序。当信息出现时,减少计数并检查 0。如果仍然 > 0,则重新启动工作程序。因为您的请求处理程序变得顺序。

在这种方法中,您可能会遇到两个线程连续引用计数的问题,因为您可能会使用锁定解锁条件。

我已经为我的应用程序采用了这种方法,它运行良好,希望它对你也一样。

于 2012-08-27T09:17:18.847 回答
0

我不是 Windows Phone 专家,但我看到它支持 TPL,所以下面的代码会很好读:

private object syncRoot =new object();
private Task latestTask;

public void EnqueueAction(System.Action action)
{
    lock (syncRoot)
    {
        if (latestTask == null)
            latestTask = Task.Factory.StartNew(action);
        else
            latestTask = latestTask.ContinueWith(tsk => action());
    }
}
于 2012-08-27T09:26:47.993 回答
0

使用可以使用信号量

  class TheClass
 {
    static SemaphoreSlim _sem = new SemaphoreSlim (3);

    static void Main()   
    {     
       for (int i = 1; i <= 5; i++) 
       new Thread (Enter).Start (i);   
    } 

    static void Enter (object name)   
    {     
      Console.WriteLine (name + " wants to enter");    
      _sem.Wait();     
      Console.WriteLine (name + " has entered!");               
      Thread.Sleep (1000 * (int) name );              
      Console.WriteLine (name + " is leaving");       
      _sem.Release();   } 
     }
 }
于 2012-08-27T09:13:06.697 回答
0

我找到了解决方案并感谢@Giedrius。仅当进程结束时,当我将项目添加到列表框时,标志 RefreshingDialogs 才设置为 true。我使用这个标志的原因是当从网络获取内容的异步操作(HttpWebRequest,方法 BeginGetRequestStream)开始时,进程状态更改为完成,但是在网络操作完成后,我需要进行 UI 操作而不仅仅是它们(解析内容并将其添加到列表框)我的解决方案是:

    private object syncRoot = new object();
    private Task latestTask;

    public void EnqueueAction(System.Action action)
    {
        lock (syncRoot)
        {
            if (latestTask == null)
            {
                downloadedDialogs = 0;
                latestTask = Task.Factory.StartNew(action);
            }
            else if(latestTask.IsCompleted && !RefreshingDialogs)
            {
                RefreshingDialogs = true;
                downloadedDialogs = 0;
                latestTask = Task.Factory.StartNew(action);
            }
        }
    }

    private void Message_Refresh_Click(object sender, EventArgs e)
    {
        Action ac = new Action(LoadDialogs2);
        EnqueueAction(ac);
    }
于 2012-08-27T12:15:25.917 回答