0
votes

I'm using silverlight to access a webservice to request some data. This call is asynchronous. I (think I) have to put this data in a class member after doing some operations on it, so I can access it later.

public class CardPrinter
{
    // The card to be printed
    private UIElement printCard;

    public void PrintStaffCard(string p_persoons)
    {
        Debug.WriteLine(p_persoons);

        foreach (string persoon in p_persoons.Split(','))
        {
            int p_persoon = Convert.ToInt32(persoon.Trim());
            this.GetStaffData(p_persoon);
        }
    }

    private void GetStaffData(int p_persoon)
    {
        PictureServiceClient proxy = new PictureServiceClient();

        proxy.GetPersonelCardInfoCompleted += this.Proxy_GetPersonelCardInfoCompleted;
        proxy.GetPersonelCardInfoAsync(p_persoon);
    }

    private void Proxy_GetPersonelCardInfoCompleted(object sender, GetPersonelCardInfoCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            Debug.WriteLine(e.Error.Message);
        }
        else
        {
            this.SendStaffCardToPrinter(e.Result);
        }
    }

    private void SendStaffCardToPrinter(CardInfo.CardInfo card)
    {
        Canvas canvas = new Canvas()

        //Do some stuff

        this.printCard = canvas;

        PrintDocument pd = new PrintDocument();
        pd.PrintPage += new EventHandler<PrintPageEventArgs>(this.Pd_PrintPage);

        pd.Print(card.accountNr, null, true);
    }

    private void Pd_PrintPage(object sender, PrintPageEventArgs e)
    {
        e.PageVisual = this.printCard;
    }

}

The problem is in the printCard variable. Sometimes it still contains the data from a previous async call in the foreach.

If I could make sure that the call in the foreach is compeletely finished there would not be a problem, but not sure how to do this and if this is the correct way to handle this.

What is the best way to handle a situation like this?

2
You could store the CardInfo 's into a Collection and wait for all the CardInfo 's to be populated. Then loop over it to PrintPage() afterwards.. - bit

2 Answers

1
votes

You can make the code easier to use by using TaskCompletionSource to convert the asynchronous methods from event based to task based. Then you can get rid of the variable and usage of the methods becomes much like using a synchronous method.

I haven't tested this, but it should be close to what you need. You may also find the following article useful. And also the following post Nested Asynchronous function in Silverlight

public class CardPrinter
{
    public void PrintStaffCard(string p_persoons)
    {
        Debug.WriteLine(p_persoons);

        foreach (string persoon in p_persoons.Split(','))
        {
            int p_persoon = Convert.ToInt32(persoon.Trim());
            var cardInfo = await this.GetStaffDataAsync(p_persoon);
            await this.SendStaffCardToPrinterAsync(cardInfo);
        }
    }

    private Task<CardInfo.CardInfo> GetStaffDataAsync(int p_persoon)
    {
        var tcs = new TaskCompletionSource<CardInfo.CardInfo>();

        PictureServiceClient proxy = new PictureServiceClient();

        proxy.GetPersonelCardInfoCompleted += (s, e) =>
        {
            if (e.Error != null)
            {
                Debug.WriteLine(e.Error.Message);
                tcs.SetException(e.Error);
            }
            else
            {
                tcs.SetResult(e.Result);
            }
        };

        proxy.GetPersonelCardInfoAsync(p_persoon);

        return tcs.Task;
    }

    private Task SendStaffCardToPrinterAsync(CardInfo.CardInfo card)
    {
        Canvas canvas = new Canvas();

        //Do some stuff

        var tcs = new TaskCompletionSource<object>();

        PrintDocument pd = new PrintDocument();

        pd.PrintPage += (s, e) => 
        {
            e.PageVisual = canvas;
            tcs.SetResult(null);
        };

        pd.Print(card.accountNr, null, true);

        return tcs.Task;
    }
}
1
votes

The GetPersonalCardInfoAsync method should have an overload where you can pass a UserState argument. You can pass your printCard there when you're making the call and access it later in your Proxy_GetPersonelCardInfoCompleted.

private void GetStaffData(int p_persoon, UIElement printCard)
{
    PictureServiceClient proxy = new PictureServiceClient();

    proxy.GetPersonelCardInfoCompleted += this.Proxy_GetPersonelCardInfoCompleted;
    proxy.GetPersonelCardInfoAsync(p_persoon, printCard);
}


private void Proxy_GetPersonelCardInfoCompleted(object sender, GetPersonelCardInfoCompletedEventArgs e)
{
    UIElement printCard = (UIElement)e.UserState;
    // do stuff 
}