为什么文件异步API会阻塞

use*_*719 3 .net c# asynchronous microsoft-metro

我正在写一个简单的城域应用程序.但是,访问文件时API会阻塞.通过阻止,我的意思是程序永远等待.创建/打开文件或文件夹最多需要几秒钟.在这种情况下,它需要永远.

当我运行程序时,它永远不会从OnTest返回.这是你得到的.我明白了.等待创建文件和文件夹才能完成.也许那不是很棒的设计.但是,这不是重点.

我的问题是:

  • 你得到相同的行为(永远阻止程序)
  • 是应该发生的还是WinRT中的错误?(我正在使用消费者预览)
  • 如果这是预期的行为,为什么需要永远?

这是XAML代码:

<Button Click="OnTest">Test</Button>
Run Code Online (Sandbox Code Playgroud)

这是C#代码:

 private async void OnTest(object sender, RoutedEventArgs e)
        {
            var t = new Cache("test1");
            t = new Cache("test2");
            t = new Cache("test3");
        }
        class Cache
        {
            public Cache(string name)
            {
                TestRetrieve(name).Wait();
            }
            public static async Task TestRetrieve(string name) 
            {
                StorageFolder rootFolder = ApplicationData.Current.LocalFolder;
                var _folder = await rootFolder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists);
                var file = await _folder.CreateFileAsync("test.xml", CreationCollisionOption.OpenIfExists);
            }
        }
Run Code Online (Sandbox Code Playgroud)

它阻止第二次调用新的Cache("test2");

Eri*_*ert 15

我没有试图运行你的程序或重现你的问题,但我可以做出有根据的猜测.

假设您自己写了以下待办事项列表:

  • 在邮箱里给妈妈写了一封信.
  • 设置闹钟,一看到她的回复就叫醒我.
  • 去睡觉.
  • 检查邮箱是否有回复.
  • 阅读回复.

现在严格按照从上到下的顺序执行该列表中的所有操作.怎么了?

问题不在于邮局或妈妈; 他们拿起你放在邮箱里的信,把它寄给妈妈,妈妈正在写她的回复,邮局正把它寄回给你.问题是你永远不会进入第四步,因为你只能完成第五启动第四步并且警报唤醒你.你会永远地睡觉,因为你基本上在等待未来的自我唤醒现在的自我.

埃里克,谢谢你的解释.

别客气.

但是,我仍然感到困惑,为什么我的代码不起作用.

好吧,让我们分解吧.什么是你的程序真的吗?让我们简化一下:

void M()
{
    Task tx = GetATask();
    tx.Wait();
}
async Task GetATask()
{
    Task ty = DoFileSystemThingAsync();
    await ty;
    DoSomethingElse();
}
Run Code Online (Sandbox Code Playgroud)

首先:什么是任务?任务是一个对象,它代表(1)要完成的工作,以及(2)任务继续的委托:任务完成需要发生的事情.

所以你打电话给GetATask.它有什么作用?嗯,它做的第一件事是它制作一个任务并将其存储在ty中.该任务表示作业"在磁盘上启动某些操作,并在完成后通知I/O完成线程".

这项任务的延续是什么?完成任务后会发生什么?需要调用DoSomethingElse.因此,编译器将await转换为一堆代码,告诉任务确保在任务完成时调用DoSomethingElse.

在设置了I/O任务的延续的那一刻,方法GetATask将一个任务返回给调用者.那是什么任务?这与存储到ty中的任务不同.返回的任务是表示作业执行GetATask方法需要执行的所有操作的任务.

这项任务的延续是什么?我们不知道!这取决于GetATask的调用者.

好的,让我们回顾一下.我们有两个任务对象.一个代表任务"在文件系统上做这件事".它将在文件系统完成其工作时完成.它的延续是"致电DoSomething".我们有第二个任务对象代表作业"在GetATask的主体中做所有事情".它将在调用DoSomethingElse返回后完成.

再说一遍:当文件I/O成功时,第一个任务将完成.当发生这种情况时,文件I/O完成线程将向主线程发送一条消息,说"嘿,你正在等待的文件I/O已经完成.我告诉你这个,因为现在是时候给你调用DoSomethingElse了".

但是主线程没有检查它的消息队列.为什么不?因为你告诉它同步等待GetATask中的所有东西,包括DoSomethingElse,都完成了.但是现在无法处理告诉您运行DoSomethingElse消息,因为您正在等待DoSomethingElse完成.

现在清楚了吗?你告诉你的线程要等到你的线程完成运行DoSomethingElse,然后再检查"请调用DoSomethingElse"是否在这个线程的工作队列中!你等到你读了妈妈的来信,但是你正在等待同步的事实意味着你没有检查你的邮箱,看看这封信是否已经到了.

在这种情况下,呼叫等待显然是错误的,因为您正在等待自己在将来做某事,而这不会起作用.但更一般地说,调用Wait 完全否定了首先出现异步的整个问题.就是不要那样做; 同时说"我想要异步"和"但我想同步等待"没有任何意义.那些是对立的.


Dre*_*rsh 7

您正在使用Wait()Cache类的构造函数.这将阻止,直到当前正在异步执行的任何内容完成.

这不是设计这个的方法.构造函数和异步没有意义.也许像这样的工厂方法方法会更好:

public class Cache
{
    private string cacheName;

    private Cache(string cacheName)
    {
        this.cacheName = cacheName;
    }

    public static async Cache GetCacheAsync(string cacheName)
    {
         Cache cache = new Cache(cacheName);

         await cache.Initialize();

         return cache;
    }

    private async void Initialize()    
    {   
            StorageFolder rootFolder = ApplicationData.Current.LocalFolder;   
            var _folder = await rootFolder.CreateFolderAsync(this.cacheName, CreationCollisionOption.OpenIfExists);   
            var file = await _folder.CreateFileAsync("test.xml", CreationCollisionOption.OpenIfExists);
   }
}
Run Code Online (Sandbox Code Playgroud)

然后你像这样使用它:

await Task.WhenAll(Cache.GetCacheAsync("cache1"), Cache.GetCacheAsync("cache2"), Cache.GetCacheAsync("cache3"));   
Run Code Online (Sandbox Code Playgroud)

  • 死锁是因为`TestReceive`试图在UI线程上恢复,但UI线程在`TestReceive`上被阻止.我建议你阅读我的[async intro post](http://tinyurl.com/7ljlmt3),它解释了`async`方法使用的"上下文".在那篇文章中,我解释了一些"推荐的最佳实践",例如尽可能调用`ConfigureAwait`,尽可能返回`Task`而不是`void`等.另见[本MSDN论坛帖子](http://tinyurl.com/cespfxs)和Stephen Toub的[Async的Zen](http://tinyurl.com/7mratbg),他实际演示了这种确切的死锁情况. (2认同)