WP7 - 在应用程序停用或关闭时正常退出bg线程时遇到问题

时间:2011-10-20 15:24:38

标签: multithreading windows-phone-7 windows-phone-7.1

我的数据密集型wp7应用程序保存数据如下:我维护一个反映所有用户活动的更改日志,每隔几秒钟,一个线程计时器就会旋转一个线程池线程,该线程将更改日志刷新到事务内的数据库。它看起来像这样:

当用户退出时,我停止计时器,刷新UI线程上的日志(不超过一两秒),然后卸载数据库。

但是,如果用户退出时工作线程活动,我无法弄清楚如何优雅地做出反应。系统似乎杀死了工作线程,因此它永远不会完成它的工作并且永远不会放弃对数据库连接的锁定,然后ui线程尝试获取锁定,并立即被系统杀死。我尝试在UI线程上设置一个标志,要求工作人员中止,但我认为工作人员在读取标志之前被中断了。一切正常,除了这个1比100的情况,其中一些用户更改最终没有保存到数据库,我似乎无法解决这个问题。

以下非常简化的代码:

private Timer _SweepTimer = new Timer(SweepCallback, null, 5000, 5000);

private volatile bool _BailOut = false;
private void SweepCallback(object state) {
    lock (db) { 
        db.startTransaction();

        foreach(var entry in changeJournal){
            //CRUD entry as appropriate
            if(_BailOut){
                db.rollbackTransaction();
                return;
            }
        }

        db.endTransaction();
        changeJournal.Clear();
    }
}

private void RespondToSystemExit(){
    _BailOut = true; //Set flag for worker to exit

    lock(db){ //In theory, should acquire the lock after the bg thread bails out
        SweepCallback(null);//Flush to db on the UI thread
        db.dismount();//App is now ready to close
    }
}

3 个答案:

答案 0 :(得分:1)

好吧,只是为了结束这个问题,我最终使用了一个手动重新发送而不是锁定,这是我最好的理解,滥用手动重复,风险和黑客,但它总比没有好。

我仍然不知道为什么我的原始代码无效。

编辑:对于后人,我正在重新发布代码以从MS论坛重现这一点:

//This is a functioning console app showing the code working as it should. Press "w" and then "i" to start and then interrupt the worker
using System;
using System.Threading;

namespace deadlocktest {
    class Program {
        static void Main(string[] args) {
            var tester = new ThreadTest();
            string input = "";
            while (!input.Equals("x")) {
                input = Console.ReadLine();

                switch (input) {
                    case "w":
                        tester.StartWorker();
                        break;
                    case "i":
                        tester.Interrupt();
                        break;
                    default:
                        return;
                }
            }
        }
    }

    class ThreadTest{
        private Object lockObj = new Object();
        private volatile bool WorkerCancel = false;

        public void StartWorker(){
            ThreadPool.QueueUserWorkItem((obj) => {
                if (Monitor.TryEnter(lockObj)) {
                    try {
                        Log("Worker acquired the lock");
                        for (int x = 0; x < 10; x++) {
                            Thread.Sleep(1200);
                            Log("Worker: tick" + x.ToString());
                            if (WorkerCancel) {
                                Log("Worker received exit signal, exiting");
                                WorkerCancel = false;
                                break;
                            }
                        }
                    } finally {
                        Monitor.Exit(lockObj);
                        Log("Worker released the lock");
                    }
                } else {
                    Log("Worker failed to acquire lock");
                }
            });
        }

        public void Interrupt() {
            Log("UI thread - Setting interrupt flag");
            WorkerCancel = true;

            if (Monitor.TryEnter(lockObj, 5000)) {
                try {
                    Log("UI thread - successfully acquired lock from worker");
                } finally {
                    Monitor.Exit(lockObj);
                    Log("UI thread - Released the lock");
                }
            } else {
                Log("UI thread - failed to acquire the lock from the worker");
            }
        }

        private void Log(string Data) {
            Console.WriteLine(string.Format("{0} - {1}", DateTime.Now.ToString("mm:ss:ffff"), Data));
        }
    }
}

这几乎是相同的代码,WP7失败,只需创建一个带有两个按钮的页面并挂钩

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using Microsoft.Phone.Controls;

namespace WorkerThreadDemo {
    public partial class MainPage : PhoneApplicationPage {
        public MainPage() {
            InitializeComponent();
        }

        private Object lockObj = new Object();
        private volatile bool WorkerCancel = false;
        private void buttonStartWorker_Click(object sender, RoutedEventArgs e) {
            ThreadPool.QueueUserWorkItem((obj) => {
                if (Monitor.TryEnter(lockObj)) {
                    try {
                        Log("Worker acquired the lock");
                        for (int x = 0; x < 10; x++) {
                            Thread.Sleep(1200);
                            Log("Worker: tick" + x.ToString());
                            if (WorkerCancel) {
                                Log("Worker received exit signal, exiting");
                                WorkerCancel = false;
                                break;
                            }
                        }
                    } finally {
                        Monitor.Exit(lockObj);
                        Log("Worker released the lock");
                    }
                } else {
                    Log("Worker failed to acquire lock");
                }
            });
        }

        private void Log(string Data) {
            Debug.WriteLine(string.Format("{0} - {1}", DateTime.Now.ToString("mm:ss:ffff"), Data));
        }

        private void buttonInterrupt_Click(object sender, RoutedEventArgs e) {
            Log("UI thread - Setting interrupt flag");
            WorkerCancel = true;

            //Thread.Sleep(3000); UNCOMMENT ME AND THIS WILL START TO WORK! 
            if (Monitor.TryEnter(lockObj, 5000)) {
                try {
                    Log("UI thread - successfully acquired lock from worker");
                } finally {
                    Monitor.Exit(lockObj);
                    Log("UI thread - Released the lock");
                }
            } else {
                Log("UI thread - failed to acquire the lock from the worker");
            }
        }
    }
}

答案 1 :(得分:0)

当您使用Application_DeactivatedApplication_Closing事件进行操作时,您的方法应该有效。 MSDN says

  

Deactivated事件有一个时间限制。该   如果申请时间超过10,则设备可以终止申请   保存瞬态的秒数。

所以如果你说它只需要几秒钟就可以了。除非文档没有讲述整个故事。或者你的工作线程退出的时间比你想象的要长。

答案 2 :(得分:0)

正如Heinrich Ulbricht已经说过你已经<= 10秒完成你的东西,但你应该阻止MainThread来获取它们。

这意味着即使你有BG线程还有很多工作要做,但你的UI线程在OnClosingEvent / OnDeactivatingEvent中什么都不做 - 你将不会得到你的10秒。

我们的应用程序实际上在关闭事件中对UI线程进行永久等待,以允许BG线程通过套接字发送一些数据。