简体   繁体   English

如何在具有返回类型的方法中调用异步方法?

[英]How to call async method inside a method which has return type?

This is windows phone 8.1 silverlight app. 这是Windows Phone 8.1 Silverlight应用程序。 I have a file association. 我有一个文件关联。 For that I have a class as 为此,我有一堂课

class AssociationUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
       //here I'm getting file ID etc..
    }

    // here I want to read the file content & determine the file type because,
    // the case is, even same file extension can contain different type of data

    switch (fileType)
    {
       //here I'm calling appropriate page according to type
    }
}

Now the problem is MapUri is overridden method so it must have a return type. 现在的问题是MapUri被重写方法,因此它必须具有返回类型。 while, OpenStreamForReadAsync() is a async method. 而OpenStreamForReadAsync()是一个异步方法。 I tried Wait() method, creating new task & then calling Start(), Wait() in it but no success. 我尝试了Wait()方法,创建新任务,然后在其中调用Start(),Wait(),但没有成功。 Currently my code is, 目前我的代码是

class AssociationUriMapper : UriMapperBase
{
    string strData = "";
    public override Uri MapUri(Uri uri)
    {
        strUri = uri.ToString();

        // File association launch
        if (strUri.Contains("/FileTypeAssociation"))
        {
            // Get the file ID (after "fileToken=").
            int nFileIDIndex = strUri.IndexOf("fileToken=") + 10;
            string strFileID = strUri.Substring(nFileIDIndex);

            string strFileName = SharedStorageAccessManager.GetSharedFileName(strFileID);
            string strIncomingFileType = Path.GetExtension(strFileName);

            fnCopyToLocalFolderAndReadContents(strFileID);

            switch (fileType)
            {
                case ".gmm":
                       //determine if gmm is text
                       if (objGMM.fnGetGMMType() == GMMFILETYPE.TXT)
                       {
                           return new Uri("/PageReadText.xaml?data=" + strData, UriKind.Relative);
                       }
                       break;
             }
        }
  }

  async void fnCopyToLocalFolderAndReadContents(string strIncomingFileId)
  {
     StorageFolder objLocalFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
     objFile = await SharedStorageAccessManager.CopySharedFileAsync(objLocalFolder, TEMP.gmm, NameCollisionOption.ReplaceExisting, strIncomingFileId);

     using (StreamReader streamReader = new StreamReader(objFile))
     {
        strData = streamReader.ReadToEnd();
     }
  }
}

The first thing I'd do is change the logic. 我要做的第一件事就是改变逻辑。 When the OS asks your app whether it supports a Uri mapping, it's expecting an immediate answer; 当操作系统询问您的应用程序是否支持Uri映射时,它会立即得到答复。 it's not expecting the app to copy and read files. 它不希望该应用程序复制和读取文件。 Usually, Uri mappings are very constant; 通常,Uri映射非常恒定。 an app either always supports one or it does not. 一个应用程序总是支持一个,或者不支持。

So, the first thing I would try to do is load all the mapping files at startup and then create the AssociationUriMapper with all the results. 因此,我要做的第一件事是在启动时加载所有映射文件, 然后使用所有结果创建AssociationUriMapper If this isn't possible, then you're almost certainly using Uri mappings for the wrong thing. 如果这不可能,那么几乎可以肯定的是,您将Uri映射用于错误的事情。 They're not supposed to be dynamic, and it is quite possible that the OS will assume that they're not dynamic. 它们不应该是动态的,操作系统很有可能会认为它们不是动态的。

That said, if you want to get it working, I think the cleanest solution would be to push the asynchronous file operations to another thread and then block on that: 就是说,如果您想使其正常运行,我认为最干净的解决方案是将异步文件操作推送到另一个线程,然后在该线程上进行阻塞:

public override Uri MapUri(Uri uri)
{
  strUri = uri.ToString();

  // File association launch
  if (strUri.Contains("/FileTypeAssociation"))
  {
    // Get the file ID (after "fileToken=").
    int nFileIDIndex = strUri.IndexOf("fileToken=") + 10;
    string strFileID = strUri.Substring(nFileIDIndex);

    string strFileName = SharedStorageAccessManager.GetSharedFileName(strFileID);
    string strIncomingFileType = Path.GetExtension(strFileName);

    var strData = Task.Run(() => CopyToLocalFolderAndReadContents(strFileID)).GetAwaiter().GetResult();

    switch (fileType)
    {
      case ".gmm":
        //determine if gmm is text
        if (objGMM.fnGetGMMType() == GMMFILETYPE.TXT)
        {
          return new Uri("/PageReadText.xaml?data=" + strData, UriKind.Relative);
        }
        break;
    }
  }
}

async Task<string> CopyToLocalFolderAndReadContentsAsync(string strIncomingFileId)
{
  StorageFolder objLocalFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
  objFile = await SharedStorageAccessManager.CopySharedFileAsync(objLocalFolder, TEMP.gmm, NameCollisionOption.ReplaceExisting, strIncomingFileId);

  using (StreamReader streamReader = new StreamReader(objFile))
  {
    return streamReader.ReadToEnd();
  }
}

I don't like it much, because it involves code that calls an async method synchronously. 我不太喜欢它,因为它涉及同步调用异步方法的代码。 But the following should work: 但是以下应该起作用:

class AssociationUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        strUri = uri.ToString();

        // File association launch
        if (strUri.Contains("/FileTypeAssociation"))
        {
            // Get the file ID (after "fileToken=").
            int nFileIDIndex = strUri.IndexOf("fileToken=") + 10;
            string strFileID = strUri.Substring(nFileIDIndex);

            string strFileName = SharedStorageAccessManager.GetSharedFileName(strFileID);
            string strIncomingFileType = Path.GetExtension(strFileName);

            string strData = fnCopyToLocalFolderAndReadContents(strFileID).Result;

            switch (fileType)
            {
                case ".gmm":
                       //determine if gmm is text
                       if (objGMM.fnGetGMMType() == GMMFILETYPE.TXT)
                       {
                           return new Uri("/PageReadText.xaml?data=" + strData, UriKind.Relative);
                       }
                       break;
             }
        }
  }

  async Task<string> fnCopyToLocalFolderAndReadContents(string strIncomingFileId)
  {
     StorageFolder objLocalFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
     objFile = await SharedStorageAccessManager.CopySharedFileAsync(objLocalFolder, TEMP.gmm, NameCollisionOption.ReplaceExisting, strIncomingFileId).ConfigureAwait(false);

     using (StreamReader streamReader = new StreamReader(objFile))
     {
        return streamReader.ReadToEnd();
     }
  }
}

For me, a bigger question is why would you implement a method like MapUri() such that it requires calls to asynchronous methods, and involves this kind of potentially time-consuming I/O. 对我来说,一个更大的问题是,为什么要实现一个像MapUri()这样的方法,使得它需要调用异步方法,并涉及这种可能耗时的I / O。 I mean, maybe that is in fact required here, but it just seems a bit off. 我的意思是,也许实际上这是必需的,但这似乎有些偏离。 Unfortunately, there's not enough context in the question for me to feel like I can offer other alternatives. 不幸的是,这个问题没有足够的上下文让我觉得我可以提供其他选择。

Unfortunately, there is no "pretty way" of overriding non-async methods. 不幸的是,没有覆盖非异步方法的“漂亮方法”。

The best you can do is make sure you add ConfigureAwait(false) to your async calls to make sure the SynchronizationContext doesn't flow and deadlock, and then access the Result property of the returned Task . 您能做的最好的事情就是确保将ConfigureAwait(false)添加到异步调用中,以确保SynchronizationContext不流动和死锁,然后访问返回的TaskResult属性。

What i would do is change the method that reads the file to return a Task<string> : 我要做的是更改读取文件的方法以返回Task<string>

async Task<string> CopyToLocalFolderAndReadContents(string strIncomingFileId)
{
    StorageFolder objLocalFolder = Windows.Storage.ApplicationData.Current
                                                                  .LocalFolder;
    objFile = await SharedStorageAccessManager
                   .CopySharedFileAsync(objLocalFolder, TEMP.gmm, 
                                        NameCollisionOption.ReplaceExisting, 
                                        strIncomingFileId)
                   .AsTask().ConfigureAwait(false);

    using (StreamReader streamReader = new StreamReader
          (await objFile.OpenStreamForReadAsync().ConfigureAwait(false)))
    {
        return await streamReader.ReadToEndAsync().ConfigureAwait(false);
    }
}

And then change the call site to: 然后将呼叫站点更改为:

string data = CopyToLocalFolderAndReadContents(strFileID).Result;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM