异步读/写锁

本文关键字:写锁 异步 | 更新日期: 2023-09-27 18:02:52

现在,我正试图使一个通用的IsoStorageManager,它将读/写和序列化/反序列化类异步,基于这个分析。但是,我知道不同的线程会请求相同的文件进行读/写。

我对这个领域的看法:

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

  2. 为写程序添加某种并发队列。如果同一个文件已经在处理中,写程序应该决定是否要取消前一个任务(重写)或从缓存中获取前一个任务并添加自己的更改(合并)。如果阅读器想要访问同一个文件,只需从队列返回数据。-似乎太复杂了

  3. 强制读取器为所有写入器使用一个线程。然后,我对访问同一文件的多次尝试没有问题。-似乎是一个临时解决方案,这是这里的主要问题。

  4. 编辑1:也许我需要一个线程安全的字典?一旦文件要写入,我会将其名称和数据存储在字典中,因此读者只需从写入器本身获取数据。

有什么建议吗?

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>();
        });
    }

对于writer也是一样,我想确保在这个过程中没有文件被访问。

EDIT3:啊哈,看起来我在这里找到了一个答案:在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");

如果按一次按钮,它或多或少是ok的(不同的文件不会同时写入,因为它应该在一个完美的世界,但现在让它这样):

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:尝试等待临界区。为单个命令(按钮点击)提供类似于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上了。应该有一个很好的公共访问(不需要登录)。会马上把所有的统计数据移到那里

异步读/写锁

据我所知,您正计划制作一个能够异步运行的Task。在我看来,最好的是互斥锁——它被设计用来保护共享资源不被多次访问:

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

互斥锁的另一个优点是in可以是全局的——您可以使用它来保护进程之间对文件的访问。

据我所知,在WP8.0中写入文件,序列化是同步完成的,然后在一个线程上运行-在获取和释放互斥锁时不会有问题。

在这里你可以找到一个好的模式

编辑

我还是不明白你想要达到什么目的,问题出在哪里。在我看来,你正在将你的反序列化重定向到ThreadPool线程,然后你可以使代码同步(它不在UI线程上运行)并使用互斥锁。可能还有很多其他的解决方案,但也许这个会有帮助:
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(); }
        }
    });
}