异步读写器锁定器

时间:2014-05-10 14:29:36

标签: c# multithreading windows-phone-7 windows-phone-8 asynchronous

现在,我正在尝试根据a generic IsoStorageManager创建this analytic,它将读取/写入并序列化/反序列化异步类。但是,我知道不同的线程会请求相同的文件进行读/写的情况。

我对这个领域的想法:

  1. 将所有Reads()/ Writes()包装成一个锁。 - 不太好,因为我不需要等待写不同的文件。

  2. 为编写者添加一些并发队列。如果同一文件已经处理,则编写者应决定是否要取消先前的任务(重写)或从缓存中获取先前的任务并添加自己的更改(合并)。如果读者想要访问同一个文件,只需从队列中返回数据即可。 - 似乎过于复杂。

  3. 强制读者为所有作家使用一个帖子。然后,我对访问同一文件的乘法尝试没有任何问题。 - 这似乎是一个临时解决方案,这是主要问题。实际上,它与1相同。

  4. EDIT1:也许我需要一个线程安全的字典?一旦要编写文件,我会将其名称和数据存储在字典中,这样读者就可以从编写器本身获取数据。

  5. 有什么建议吗?

    EDIT2:

    我正在使用任务

    public static async Task<T> ReadJsonAsyncTask<T>(this JsonTextReader reader)
        {
            return await TaskEx.Run(() => jsonSerializer.Deserialize<T>(reader));
        }
    
    像这样

    public static async Task<T> ReadJsonEx<T>(String fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                return default(T);
    
            return await await Task.Factory.StartNew(async () =>
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var sr = new StreamReader(stream))
                using (var jr = new JsonTextReader(sr))
                    return await jr.ReadJsonAsyncTask<T>();
            });
        }
    

    对于编者而言,我希望确保在此过程中不会访问任何文件。

    EDIT3:啊哈,看起来我在这里找到了答案:Easy way to save game in WP7 Silverlight?

    EDIT4:仅适用于同步调用。不是我的情况。 :(

    EDIT5:经过一整天的搜索,我找到了AsyncReaderWriterLock。用法很简单:

    private static readonly AsyncReaderWriterLock readerLocker = new AsyncReaderWriterLock(); 
    
    public static async Task<T> ReadJsonEx<T>(String fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                return default(T);
    
            return await await Task.Factory.StartNew(async () =>
            {
                using (var locker = await readLocker.ReaderLockAsync())
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var sr = new StreamReader(stream))
                using (var jr = new JsonTextReader(sr))
                    return await jr.ReadJsonAsyncTask<T>();
            });
        }
    

    有时它有效 - 有时 - 不是。

    EDIT6:好的,这里有一些关于我的AsyncReaderWriterLock测试用例的更多细节。我有像之前提到的读者和使用自己的AsyncReaderWriterLock的编写器。我有一个带有进度条和按钮的页面。按钮命令是:

    SimpleLogger.WriteLine("Starting generation...");
    
            var list = new List<Order>();
            //for (var i = 0; i < 10; i++)
                list.Add(GenerateOrder());
    
    
            SimpleLogger.WriteLine("Writing 5 times the same file...");
    
            var res1 = await IsoStorageManager.WriteJsonEx(fileName1, list);
            var res2 = await IsoStorageManager.WriteJsonEx(fileName1, list);
            var res3 = await IsoStorageManager.WriteJsonEx(fileName1, list);
            var res4 = await IsoStorageManager.WriteJsonEx(fileName1, list);
            var res5 = await IsoStorageManager.WriteJsonEx(fileName1, list);
    
            SimpleLogger.WriteLine("Writing 5 different files");
    
            var res11 = await IsoStorageManager.WriteJsonEx(fileName1, list);
            var res12 = await IsoStorageManager.WriteJsonEx(fileName2, list);
            var res13 = await IsoStorageManager.WriteJsonEx(fileName3, list);
            var res14 = await IsoStorageManager.WriteJsonEx(fileName4, list);
            var res15 = await IsoStorageManager.WriteJsonEx(fileName5, list);
    
            SimpleLogger.WriteLine("Reading 5 times the same");
    
            var res21 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
            var res22 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
            var res23 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
            var res24 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
            var res25 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
    
            SimpleLogger.WriteLine("Reading 5 times different");
    
            var res31 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName1);
            var res32 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName2);
            var res33 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName3);
            var res34 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName4);
            var res35 = await IsoStorageManager.ReadJsonEx<List<Order>>(fileName5);
    
            SimpleLogger.WriteLine("Done");
    

    如果要按一次按钮,它或多或少都可以(不同的文件不是同时写的,因为它应该在一个完美的世界中,但现在就是这样):

    09:03:38.262 [00:00:00.000] Starting generation...
    09:03:38.300 [00:00:00.025] Writing 5 times the same file...
    09:03:43.126 [00:00:04.811] Writing 5 different files
    09:03:47.303 [00:00:04.163] Reading 5 times the same
    09:03:50.194 [00:00:02.871] Reading 5 times different
    09:03:53.341 [00:00:03.130] Done
    

    如果按下按钮几次以模拟高负荷和混合写入/读取的东西,我得到了这个输出:

    08:51:52.680 [00:00:00.000] Starting generation...
    08:51:52.722 [00:00:00.028] Writing 5 times the same file...
    08:51:52.795 [00:00:00.057] Starting generation...
    08:51:52.854 [00:00:00.043] Writing 5 times the same file...
    08:51:52.892 [00:00:00.023] Starting generation...
    08:51:52.922 [00:00:00.016] Writing 5 times the same file...
    08:51:52.943 [00:00:00.006] Starting generation...
    08:51:52.973 [00:00:00.016] Writing 5 times the same file...
    08:52:06.009 [00:00:13.022] Writing 5 different files
    08:52:06.966 [00:00:00.942] Writing 5 different files
    08:52:07.811 [00:00:00.778] Writing 5 different files
    08:52:08.513 [00:00:00.689] Writing 5 different files
    08:52:22.115 [00:00:13.567] Reading 5 times the same
    08:52:22.887 [00:00:00.755] Reading 5 times the same
    08:52:23.773 [00:00:00.754] Reading 5 times the same
    

    using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))

    中的例外情况
    System.IO.IOException occurred
    _HResult=-2147024864
    _message=[IO.IO_SharingViolation_File] 
    Arguments: Folder//TestFile1.txt
    Debugging resource strings are unavailable. Often the key and arguments provide sufficient information to diagnose the problem. See http://go.microsoft.com/fwlink/?linkid=106663&Version=4.0.50829.0&File=mscorlib.dll&Key=IO.IO_SharingViolation_File
    HResult=-2147024864
    Message=[IO.IO_SharingViolation_File]
    Arguments: Folder//TestFile1.txt
    Debugging resource strings are unavailable. Often the key and arguments provide sufficient information to diagnose the problem. See http://go.microsoft.com/fwlink/?linkid=106663&Version=4.0.50829.0&File=mscorlib.dll&Key=IO.IO_SharingViolation_File
    Source=mscorlib
    StackTrace:
        at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    InnerException: 
    

    EDIT7:尝试awaitable critical section。提供类似于单个命令(按钮点击)的AsyncReaderWriterLock结果:

    03:12:05.213 [00:00:00.000] Starting generation...
    03:12:05.252 [00:00:00.023] Writing 5 times the same file...
    03:12:09.894 [00:00:04.626] Writing 5 different files
    03:12:13.700 [00:00:03.792] Reading 5 times the same
    03:12:16.831 [00:00:03.115] Reading 5 times different
    03:12:20.032 [00:00:03.171] Done
    

    但是碰撞测试(4个快速按钮点击)似乎更稳定:它设法完成任务而没有崩溃。

    EDIT8:将所有狗屎移到Google Spreadshit。应该有一个很好的公共访问(不需要登录)。将在一段时间内移动所有统计数据。

1 个答案:

答案 0 :(得分:2)

据我所知,你打算制作一个Task,你可以异步运行。 IMO最好的就是Mutex - 它旨在保护共享资源免受多种访问:

  

当两个或多个线程需要同时访问共享资源时,系统需要一个同步机制来确保一次只有一个线程使用该资源。 Mutex是一个同步原语,它只允许对一个线程的共享资源进行独占访问。如果某个线程获取了一个互斥锁,那么想要获取该互斥锁的第二个线程将被挂起,直到第一个线程释放该互斥锁。

Mutex 的另一个优点是可以是Global - 您可以使用它来保护进程之间对文件的访问。

据我记得在WP8.0中写入文件时,序列化是同步完成的,然后在一个线程上运行 - 获取和释放Mutex没有问题。

Here you can find a good pattern.

修改

我仍然没有得到你想要达到的目标以及问题所在。 IMO您正在将反序列化重定向到ThreadPool线程,然后您可以使代码同步(它不在UI线程上运行)并使用Mutex。可能还有很多其他解决方案,但也许这会有所帮助:

public static async Task<T> ReadJsonEx<T>(String fileName)
{
    if (String.IsNullOrEmpty(fileName)) return default(T);

    string mutexName = "dependantOnApp" + fileName;
    return await Task.Run<T>(() =>
    {
        using (Mutex myMutex = new Mutex(false, mutexName))
        {
            try
            {
                myMutex.WaitOne();
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var sr = new StreamReader(stream))
                using (var jr = new JsonTextReader(sr))
                    return jsonSerializer.Deserialize<T>(jr);
            }
            catch { throw new Exception("Exception"); }
            finally { myMutex.ReleaseMutex(); }
        }
    });
}