【问题标题】:Call to Task<> method inside LINQ and return data在 LINQ 中调用 Task<> 方法并返回数据
【发布时间】:2014-04-18 10:45:33
【问题描述】:

我有一个问题,我在 LINQ 语句中调用 Task&lt;&gt; 方法并尝试从 Task&lt;&gt; 方法返回数据(图像)。我无法让Task&lt;&gt; 方法返回数据(图像),我收到以下错误:

无法隐式转换类型“System.Threading.Tasks.Task” 到“System.Uri”。

这是带有 LINQ 语句的 DownloadStringCompleted 方法,其中我调用 GetTest Task 方法:

private async void GetGamesListRequestCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
    var feedXml = XDocument.Parse(e.Result);

    var gameData = feedXml.Root.Descendants("Game").Select(x => new GetGamesList
    {
        ID = (int)x.Element("id"),
        GameTitle = (string)x.Element("GameTitle"),
        ReleaseDate = (string)x.Element("ReleaseDate"),
        Platform = (string)x.Element("Platform"),
        Front = GetTest((int)x.Element("id")), // THE METHOD WITH PROBLEM.
    })
    .ToList();

    foreach (var item in gameData)
    {
        GetGamesListItems.Add(item);
    }
}
}

Task GetTest 方法在尝试返回数据(图像)时遇到问题:

public Task<string> GetTest(int id)
{
    var tcs = new TaskCompletionSource<string>();
    var client = new WebClient();
    client.DownloadStringCompleted += (s, e) =>
    {
        if (e.Error == null)
        {
            var feedXml = XDocument.Parse(e.Result);

            var gameData = feedXml.Root.Descendants("Images").Select(x => new GetArt
            {
                BoxArtFrontThumb = new Uri(GetBoxArtFront(x)),
            })
            .ToList();

            foreach (var item in gameData) GetArtItems.Add(item);
            foreach (var i in GetArtItems)
            {
                tcs.SetResult("http://thegamesdb.net/banners/" + i.BoxArtFrontThumb.ToString());
            }
        }
        else
        {
            tcs.SetException(e.Error);
        }
    };

    client.DownloadStringAsync(new Uri("http://thegamesdb.net/api/GetArt.php?id=" + id.ToString()));
    return tcs.Task;
}

我存储图像的 ObservableCollection: 私有 ObservableCollection _GetArtItems = new ObservableCollection();

public ObservableCollection<GetArt> GetArtItems
{
    get
    {
        return this._GetArtItems;
    }
}

我从 XML 获取图像的地方:

private static string GetBoxArtFront(XElement gameNode)
{
    return "http://thegamesdb.net/banners/" + (string)gameNode.Descendants("boxart")
        .FirstOrDefault(b => (string)b.Attribute("side") == "front");
}

这是我用来存储数据(图像)的类:

public class GetArt
{
    public Uri BoxArtFrontThumb { get; set; }
}

这是我想在其中显示数据(图像)的 LongListSelector:

<phone:LongListSelector Name="llsGameList" Background="#242424" ItemsSource="{Binding}" Tap="llsGameList_Tap" Margin="0,90,0,0">
                    <phone:LongListSelector.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <toolkit:ContextMenuService.ContextMenu>
                                    <toolkit:ContextMenu Name="ContextMenu">
                                        <toolkit:MenuItem 
                            Name="addToFavorite"  
                            Header="Add to favorite" 
                            Click="addToFavorite_Click"/>
                                    </toolkit:ContextMenu>
                                </toolkit:ContextMenuService.ContextMenu>
                                <StackPanel>
                                    <Border Background="{StaticResource PhoneAccentBrush}" 
        Padding="{StaticResource PhoneTouchTargetOverhang}"
        Margin="{StaticResource PhoneTouchTargetOverhang}">
                                        <TextBlock Name="tblGameTitle" Style="{StaticResource PhoneTextGroupHeaderStyle}" ManipulationStarted="tblGameTitle_ManipulationStarted" ManipulationCompleted="tblGameTitle_ManipulationCompleted">
                                        <Run Text="{Binding GameTitle}"></Run>
                                        </TextBlock>
                                    </Border>
                                    <Image Source="{Binding Front}" Height="200" Width="200"></Image> // HERE IM BINDING TO MY FRONT PROPERTY TO SHOW THE IMAGES
                                    <TextBlock TextWrapping="Wrap" Foreground="YellowGreen" Style="{StaticResource PhoneTextNormalStyle}" Padding="{StaticResource PhoneTouchTargetOverhang}" 
        FontSize="{StaticResource PhoneFontSizeNormal}">
                                        <Run Text="Platform: "></Run>
                                        <Run Text="{Binding Platform}"></Run>
                                    </TextBlock>
                                    <TextBlock Foreground="YellowGreen" Style="{StaticResource PhoneTextNormalStyle}" Padding="{StaticResource PhoneTouchTargetOverhang}" 
        FontSize="{StaticResource PhoneFontSizeNormal}">
                                        <Run Text="Release Date: "></Run>
                                        <Run Text="{Binding ReleaseDate}"></Run>
                                    </TextBlock>
                                </StackPanel>
                            </Grid>
                        </DataTemplate>
                    </phone:LongListSelector.ItemTemplate>
                </phone:LongListSelector>

这是我的 DataContext,它获取 gd.GetGamesListItems,其中包含我想在 LongListSelector 中显示的图像:

public MainPage()
    {
        InitializeComponent();
        llsGameList.DataContext = gd.GetGamesListItems;
    }

我希望有人可以帮助我:)。谢谢。

【问题讨论】:

    标签: c# xml linq windows-phone-8 task-parallel-library


    【解决方案1】:

    每当您拥有Task&lt;T&gt; 时,您都需要花一点时间考虑您希望如何(异步)等待结果。请记住,Task&lt;T&gt; 代表未来结果,而不是当前结果。

    根据@Sriram 的回答,一个选项将(异步)一次等待一个结果,并在结果到达时将它们添加到列表中。另一种选择是这样做:

    var feedXml = XDocument.Parse(e.Result);
    var gameDataTasks = feedXml.Root.Descendants("Game").Select(
        async x => new GetGamesList
        {
          ID = (int)x.Element("id"),
          GameTitle = (string)x.Element("GameTitle"),
          ReleaseDate = (string)x.Element("ReleaseDate"),
          Platform = (string)x.Element("Platform"),
          Front = new Uri(await GetTestAsync((int)x.Element("id"))),
        }).ToList();
    var gameData = await Task.WhenAll(gameDataTasks);
    foreach (var item in gameData)
    {
      GetGamesListItems.Add(item);
    }
    

    这将启动所有元素的所有GetTestAsync 调用,然后(异步)等待它们全部完成(并检索结果)。

    其他说明:

    • 我确实将您的方法重命名为 GetTestAsync 以符合 Task-based Asynchronous Pattern
    • 如果您使用较新的 HttpClient,我发现代码更简洁,它内置了对 await 的支持。

    这是一个使用HttpClient的例子:

    public async Task<string> GetTestAsync(int id)
    {
      var client = new HttpClient();
      var result = await client.GetStringAsync("http://thegamesdb.net/api/GetArt.php?id=" + id);
      var feedXml = XDocument.Parse(result);
      var gameData = feedXml.Root.Descendants("Images").Select(x => new GetArt
      {
        BoxArtFrontThumb = new Uri(GetBoxArtFront(x)),
      }).ToList();
      foreach (var item in gameData) GetArtItems.Add(item);
      return "http://thegamesdb.net/banners/" + gameData.Single().BoxArtFrontThumb.ToString()
    }
    

    【讨论】:

    • 感谢您的回答:)。我会尝试看看这是否有效并回发:)。
    • 我尝试了代码,但我得到了 2 个错误:在 GetTestAsync 方法中我得到这个错误:并非所有代码路径都返回一个值,并且在调用 Front = await GetTestAsync((int)x.Element ("id")) 我收到此错误:无法将类型字符串隐式转换为 System.Uri :(
    • 是的,您的原始代码存在一些问题。如果不深入了解您的代码,我无法修复它,但是这两个错误都应该有简单的解决方案。
    • 我编辑了我的问题,我希望这有助于解释我想要做什么:)
    • 不,但是this blog post might help
    【解决方案2】:

    要修复它,您可以调用Task.Result,但随后方法将同步执行,等待异步操作。

    您必须将调用方法也标记为异步并异步等待GetTest

    var feedXml = XDocument.Parse(e.Result);
    
    List<GetGamesList> gameData = new List<GetGamesList>();
    foreach (var item  in feedXml.Root.Descendants("Game"))
    {
        var gl = new GetGamesList();
        gl.ID = (int)x.Element("id");
        gl.GameTitle = (string)x.Element("GameTitle");
        gl.ReleaseDate = (string)x.Element("ReleaseDate");
        gl.Platform = (string)x.Element("Platform");
        gl.Front = new Uri(await GetTest((int)x.Element("id")));//Note the await keyword
    
        gameData.Add(gl);
    }
    foreach (var item in gameData)
    {
        GetGamesListItems.Add(item);
    }
    

    【讨论】:

    • 谢谢,我会试试这个。那么我还需要更改我的 Task GetTest 方法吗?
    • 该死 :(,我测试了您发布的代码,但我在此行收到错误:tcs.SetResult("http://thegamesdb.net/banners/" + i.BoxArtFrontThumb.ToString()); 并带有此错误消息:尝试将任务转换为最终状态已经完成了。
    • 糟糕,我刚刚注意到您的GetTest 实现是错误的,您正在循环调用tcs.SetResult。但只允许一次,因为单个任务只能完成一次。
    • 嗯,好的,有什么想法吗?
    • 我不知道你在循环设置tcs.SetResult 中做了什么。 Task&lt;string&gt; 表示它将在将来返回一个字符串,但您要设置 SetResult 多次。您是否打算为单个任务返回多个字符串?如果是这样,您需要Task&lt;string[]&gt; 而不是Task&lt;string&gt;。我先睡了,希望我的评论对你有帮助。如果有什么我会去看看早上。 :)
    【解决方案3】:

    我不确定我是否认为这样调用任务是个好主意,但是...问题是您没有从任务中得到结果。

    您需要:

    Front = GetTest().Result
    

    Front = await GetTest()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-07
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 2012-12-13
      相关资源
      最近更新 更多