Question

I have a problem where im calling a Task<> method inside a LINQ statement and trying to return the data(images) from the Task<> method. I can't get the Task<> method to return the data(images), I get the following error:

Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Uri'.

Here is the DownloadStringCompleted method with LINQ statement, where im calling the GetTest Task<> method:

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

The Task<> GetTest method where i get the problem when trying to return data(images):

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

The ObservableCollection where i store the images: private ObservableCollection _GetArtItems = new ObservableCollection();

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

Where i get the images from XML:

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

Here is the class i use to store the data(images):

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

Here is my LongListSelector i would like to show the data(images) in:

<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>

This is my DataContext which gets gd.GetGamesListItems which contains the images i would like to show in LongListSelector:

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

I hope there is someone that can help me :). Thanks.

Was it helpful?

Solution

Whenever you have a Task<T>, you need to spend a bit of time thinking about how you want to (asynchronously) wait for the result. Remember, a Task<T> represents a future result, not a current result.

One option is as per @Sriram's answer, which will (asynchronously) wait for the results one at a time, adding them to a list as they arrive. Another option is to do something like this:

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

This will start all of the GetTestAsync calls for all the elements, and then (asynchronously) wait for them all to complete (and retrieve the results).

Other notes:

  • I did rename your method to GetTestAsync to conform with the Task-based Asynchronous Pattern.
  • I find the code is cleaner if you use the newer HttpClient, which has built-in support for await.

Here's an example of using 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()
}

OTHER TIPS

To fix it you can call Task.Result but then method will execute synchronously waiting for async operation.

You have to mark the calling method also async and wait for GetTest asynchronously.

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

I'm not sure I think calling tasks like that is a good idea but...The problem is your not getting the result from the task.

You need to either:

Front = GetTest().Result

or

Front = await GetTest()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top