RMR
RMR

Reputation: 629

How to call async method from not async method?

I am working in xamarin forms. I am trying to bind Web service in calender control. refer following link for calendar control (XamForms.Controls.Calendar).

https://github.com/rebeccaXam/XamForms.Controls.Calendar

First Function is to Create 7*6=42 labels and buttons then "callWebService" method to call service function which is async method to get response from service.

protected void FillCalendarWindows()
        {
            try
            {
                for (int r = 0; r < 6; r++)
                {
                    for (int c = 0; c < 7; c++)
                    {
                        if (r == 0)
                        {
                            labels.Add(new Label
                            {
                                HorizontalOptions = LayoutOptions.Center,
                                VerticalOptions = LayoutOptions.Center,
                                TextColor = Color.Black,
                                FontSize = 18,
                                FontAttributes = FontAttributes.Bold
                            });
                            DayLabels.Children.Add(labels.Last(), c, r);
                        }
                        buttons.Add(new CalendarButton
                        {
                            BorderRadius = 0,
                            BorderWidth = BorderWidth,
                            BorderColor = BorderColor,
                            FontSize = DatesFontSize,
                            BackgroundColor = DatesBackgroundColor,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            VerticalOptions = LayoutOptions.FillAndExpand
                        });
                        buttons.Last().Clicked += DateClickedEvent;
                        MainCalendar.Children.Add(buttons.Last(), c, r);
                    }
                }
                flag = 1;
                //Device.BeginInvokeOnMainThread(() => CallWebService(StartDate.Month, StartDate.Year));
                CallWebService(StartDate.Month, StartDate.Year);
                //CallServiceInNewTask(StartDate.Month, StartDate.Year);
                //Device.BeginInvokeOnMainThread(() => ChangeCalendar(CalandarChanges.All));

            }
            catch (Exception e)
            {

            }
        }

Second Function is to "callWebService" function where I collect response in list collection object then call "ChangeClaendar" function which is used to bind labels and button text and fill appropriate color.

public async void CallWebService(int Month, int Year)
        {
            try
            {
                var response = await GetResponseFromWebService.GetResponse<ServiceClasses.RootObject_AttendanceTable>(ServiceURL.GetAttendanceTableList + "Month=" + Month + "&Year=" + Year + "&EmpCd=" + _empCode);
                if (response.Flag == true)
                {
                    if (ListObjAttendanceTblList == null)
                    {
                        ListObjAttendanceTblList = new List<LstAttendanceDtl>();
                    }
                    for (int i = 0; i < response.lstAttendanceDtl.Count; i++)
                    {
                        var objAttendanceTableList = new LstAttendanceDtl();

                        objAttendanceTableList.AttendanceDt = response.lstAttendanceDtl[i].AttendanceDt;
                        objAttendanceTableList.EarlyDeparture = response.lstAttendanceDtl[i].EarlyDeparture;
                        objAttendanceTableList.InTime = response.lstAttendanceDtl[i].EarlyDeparture;
                        objAttendanceTableList.LateArrival = response.lstAttendanceDtl[i].EarlyDeparture;
                        objAttendanceTableList.OutTime = response.lstAttendanceDtl[i].OutTime;
                        objAttendanceTableList.OverTime = response.lstAttendanceDtl[i].OverTime;
                        objAttendanceTableList.Reason = response.lstAttendanceDtl[i].Reason;
                        objAttendanceTableList.Remark = response.lstAttendanceDtl[i].Remark;
                        objAttendanceTableList.Shift = response.lstAttendanceDtl[i].Shift;
                        objAttendanceTableList.WorkingHrs = response.lstAttendanceDtl[i].WorkingHrs;

                        ListObjAttendanceTblList.Add(objAttendanceTableList);
                    }
                }
                else
                {
                }
                if (flag == 1)
                {
                    ChangeCalendar(CalandarChanges.All);
                }
                else
                {
                    ChangeCalendar(CalandarChanges.StartDate);
                }
            }
            catch (WebException e)
            {

            }
        } 

Third function is to "ChangeCalendar"

protected void ChangeCalendar(CalandarChanges changes)
        {
            try
            {
                if (changes.HasFlag(CalandarChanges.StartDate))
                {
                    Device.BeginInvokeOnMainThread(() => CenterLabel.Text = StartDate.ToString(TitleLabelFormat));
                }
                var start = CalendarStartDate;
                var beginOfMonth = false;
                var endOfMonth = false;
                for (int i = 0; i < buttons.Count; i++)
                {
                    endOfMonth |= beginOfMonth && start.Day == 1;
                    beginOfMonth |= start.Day == 1;

                    LstAttendanceDtl objAttendanceDtl = ListObjAttendanceTblList.Find(s => s.AttendanceDt.Equals(start.Date.ToString("dd/MM/yyyy")));
                    string remarks = string.Empty;

                    if (i < 7 && WeekdaysShow && changes.HasFlag(CalandarChanges.StartDay))
                    {
                        Device.BeginInvokeOnMainThread(() => labels[i].Text = start.ToString(WeekdaysFormat));
                        //labels[i].Text = start.ToString(WeekdaysFormat);
                        //DateTime d = Convert.ToDateTime(objAttendanceDtl.AttendanceDt).Date; 
                    }
                    if (changes.HasFlag(CalandarChanges.All))
                    {
                        Device.BeginInvokeOnMainThread(()=>buttons[i].Text = string.Format("{0}", start.Day));
                        //buttons[i].Text = string.Format("{0}", start.Day);
                    }
                    else
                    {
                        Device.BeginInvokeOnMainThread(() => buttons[i].TextWithoutMeasure = string.Format("{0}", start.Day));
                    }

                    buttons[i].Date = start;
                    var isInsideMonth = beginOfMonth && !endOfMonth;
                    if (objAttendanceDtl != null)
                    {
                        remarks = objAttendanceDtl.Remark;

                        if ((remarks.ToLower()).Trim() == stringFullDay.ToLower().Trim())
                        {
                            SetButtonPresent(buttons[i], isInsideMonth);
                        }
                        else if (remarks.ToLower().Trim() == stringAbsent.ToLower().Trim())
                        {
                            SetButtonAbsent(buttons[i], isInsideMonth);
                        }
                        else if (remarks.ToLower().Trim() == stringWeekOff.ToLower().Trim())
                        {
                            SetButtonWeekendMood(buttons[i], isInsideMonth);
                        }
                        else if (remarks.ToLower().Trim() == stringHolidays.ToLower().Trim())
                        {
                            SetButtonHolidays(buttons[i], isInsideMonth);
                        }
                        else if (remarks.ToLower().Trim() == stringSecondhalfAbsent.ToLower().Trim() ||
                            remarks.ToLower().Trim() == stringFirsthalfAbsent.ToLower().Trim())
                        {
                            SetButtonHalfDayMood(buttons[i], isInsideMonth);
                        }
                        else
                        {
                            SetButtonDisabled(buttons[i]);
                        }
                    }
                    else
                    {
                        SetButtonOutSideMonth(buttons[i]);
                    }
                    SpecialDate sd = null;
                    if (SpecialDates != null)
                    {
                        sd = SpecialDates.FirstOrDefault(s => s.Date.Date == start.Date);
                    }

                    if (sd != null)
                    {
                        SetButtonSpecial(buttons[i], sd);
                    }
                    else if (SelectedDate.HasValue && start.Date == SelectedDate.Value.Date)
                    {
                        SetButtonSelected(buttons[i], isInsideMonth);
                    }
                    start = start.AddDays(1);
                }
            }
            catch (Exception e)
            {

            }
        }

Problems are :

1. In "Changecalendar" function when I try to fill list of labels directly

labels[i].Text = start.ToString(WeekdaysFormat);

it showing me an error

"UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread." So to remove this error I wrote

Device.BeginInvokeOnMainThread(() => labels[i].Text = start.ToString(WeekdaysFormat));

Device.BeginInvokeOnMainThread(()=>buttons[i].Text = string.Format("{0}", start.Day));

Device.BeginInvokeOnMainThread(() => buttons[i].TextWithoutMeasure = string.Format("{0}", start.Day));

but it showing me an error

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

2. if I put both function "CallWebService" and "ChangeCalendar" in "FillCalendarWindow" one after one then list object is not bound and control is come out of the function and directly ChangeCalendar function call and giving me null reference object.

Upvotes: 1

Views: 1041

Answers (4)

Atul
Atul

Reputation: 440

Try following

protected async Task FillCalendarWindows()
        {
            try
            {
                for (int r = 0; r < 6; r++)
                {
                    for (int c = 0; c < 7; c++)
                    {
                        if (r == 0)
                        {
                            labels.Add(new Label
                            {
                                HorizontalOptions = LayoutOptions.Center,
                                VerticalOptions = LayoutOptions.Center,
                                TextColor = Color.Black,
                                FontSize = 18,
                                FontAttributes = FontAttributes.Bold
                            });
                            DayLabels.Children.Add(labels.Last(), c, r);
                        }
                        buttons.Add(new CalendarButton
                        {
                            BorderRadius = 0,
                            BorderWidth = BorderWidth,
                            BorderColor = BorderColor,
                            FontSize = DatesFontSize,
                            BackgroundColor = DatesBackgroundColor,
                            HorizontalOptions = LayoutOptions.FillAndExpand,
                            VerticalOptions = LayoutOptions.FillAndExpand
                        });
                        buttons.Last().Clicked += DateClickedEvent;
                        MainCalendar.Children.Add(buttons.Last(), c, r);
                    }
                }
                flag = 1;
                //Device.BeginInvokeOnMainThread(() => CallWebService(StartDate.Month, StartDate.Year));
                await CallWebService(StartDate.Month, StartDate.Year);
                //CallServiceInNewTask(StartDate.Month, StartDate.Year);
                //Device.BeginInvokeOnMainThread(() => await ChangeCalendar(CalandarChanges.All));

            }
            catch (Exception e)
            {

            }
        }

Your Webservice should be like

public async Task CallWebService(int Month, int Year)
    {
        try
        {
            var response = await GetResponseFromWebService.GetResponse<ServiceClasses.RootObject_AttendanceTable>(ServiceURL.GetAttendanceTableList + "Month=" + Month + "&Year=" + Year + "&EmpCd=" + _empCode);
            if (response.Flag == true)
            {
                if (ListObjAttendanceTblList == null)
                {
                    ListObjAttendanceTblList = new List<LstAttendanceDtl>();
                }
                for (int i = 0; i < response.lstAttendanceDtl.Count; i++)
                {
                    var objAttendanceTableList = new LstAttendanceDtl();

                    objAttendanceTableList.AttendanceDt = response.lstAttendanceDtl[i].AttendanceDt;
                    objAttendanceTableList.EarlyDeparture = response.lstAttendanceDtl[i].EarlyDeparture;
                    objAttendanceTableList.InTime = response.lstAttendanceDtl[i].EarlyDeparture;
                    objAttendanceTableList.LateArrival = response.lstAttendanceDtl[i].EarlyDeparture;
                    objAttendanceTableList.OutTime = response.lstAttendanceDtl[i].OutTime;
                    objAttendanceTableList.OverTime = response.lstAttendanceDtl[i].OverTime;
                    objAttendanceTableList.Reason = response.lstAttendanceDtl[i].Reason;
                    objAttendanceTableList.Remark = response.lstAttendanceDtl[i].Remark;
                    objAttendanceTableList.Shift = response.lstAttendanceDtl[i].Shift;
                    objAttendanceTableList.WorkingHrs = response.lstAttendanceDtl[i].WorkingHrs;

                    ListObjAttendanceTblList.Add(objAttendanceTableList);
                }
            }
            else
            {
            }
            if (flag == 1)
            {
               await ChangeCalendar(CalandarChanges.All);
            }
            else
            {
               await ChangeCalendar(CalandarChanges.StartDate);
            }
        }
        catch (WebException e)
        {

        }
    } 

And Your ChnageCalender method should be like

protected async Task ChangeCalendar(CalandarChanges changes)
    {
        try
        {
            if (changes.HasFlag(CalandarChanges.StartDate))
            {
                Device.BeginInvokeOnMainThread(() => CenterLabel.Text = StartDate.ToString(TitleLabelFormat));
            }
            var start = CalendarStartDate;
            var beginOfMonth = false;
            var endOfMonth = false;
            for (int i = 0; i < buttons.Count; i++)
            {
                endOfMonth |= beginOfMonth && start.Day == 1;
                beginOfMonth |= start.Day == 1;

                LstAttendanceDtl objAttendanceDtl = ListObjAttendanceTblList.Find(s => s.AttendanceDt.Equals(start.Date.ToString("dd/MM/yyyy")));
                string remarks = string.Empty;

                if (i < 7 && WeekdaysShow && changes.HasFlag(CalandarChanges.StartDay))
                {
                    Device.BeginInvokeOnMainThread(() => labels[i].Text = start.ToString(WeekdaysFormat));
                    //labels[i].Text = start.ToString(WeekdaysFormat);
                    //DateTime d = Convert.ToDateTime(objAttendanceDtl.AttendanceDt).Date; 
                }
                if (changes.HasFlag(CalandarChanges.All))
                {
                    Device.BeginInvokeOnMainThread(()=>buttons[i].Text = string.Format("{0}", start.Day));
                    //buttons[i].Text = string.Format("{0}", start.Day);
                }
                else
                {
                    Device.BeginInvokeOnMainThread(() => buttons[i].TextWithoutMeasure = string.Format("{0}", start.Day));
                }

                buttons[i].Date = start;
                var isInsideMonth = beginOfMonth && !endOfMonth;
                if (objAttendanceDtl != null)
                {
                    remarks = objAttendanceDtl.Remark;

                    if ((remarks.ToLower()).Trim() == stringFullDay.ToLower().Trim())
                    {
                        SetButtonPresent(buttons[i], isInsideMonth);
                    }
                    else if (remarks.ToLower().Trim() == stringAbsent.ToLower().Trim())
                    {
                        SetButtonAbsent(buttons[i], isInsideMonth);
                    }
                    else if (remarks.ToLower().Trim() == stringWeekOff.ToLower().Trim())
                    {
                        SetButtonWeekendMood(buttons[i], isInsideMonth);
                    }
                    else if (remarks.ToLower().Trim() == stringHolidays.ToLower().Trim())
                    {
                        SetButtonHolidays(buttons[i], isInsideMonth);
                    }
                    else if (remarks.ToLower().Trim() == stringSecondhalfAbsent.ToLower().Trim() ||
                        remarks.ToLower().Trim() == stringFirsthalfAbsent.ToLower().Trim())
                    {
                        SetButtonHalfDayMood(buttons[i], isInsideMonth);
                    }
                    else
                    {
                        SetButtonDisabled(buttons[i]);
                    }
                }
                else
                {
                    SetButtonOutSideMonth(buttons[i]);
                }
                SpecialDate sd = null;
                if (SpecialDates != null)
                {
                    sd = SpecialDates.FirstOrDefault(s => s.Date.Date == start.Date);
                }

                if (sd != null)
                {
                    SetButtonSpecial(buttons[i], sd);
                }
                else if (SelectedDate.HasValue && start.Date == SelectedDate.Value.Date)
                {
                    SetButtonSelected(buttons[i], isInsideMonth);
                }
                start = start.AddDays(1);
            }
        }
        catch (Exception e)
        {

        }
    }

Upvotes: 2

Yuri S
Yuri S

Reputation: 5370

I see at least 2 problems from your comments

  1. "If(response.flag==true) (control goes out of the function) and directly call ChangeCalendar() - obviously because you don't await.

  2. Your number of buttons (42) is not the same as number of labels (7), so when you try to go labels[i] and buttons[i] with the same "i" you are getting ArgumentOutOfRangeException of index for labels. Note if(r==0) which limits number of labels to 7.

            for (int r = 0; r < 6; r++)
            {
                for (int c = 0; c < 7; c++)
                {
                    if (r == 0)
                    {
                        labels.Add(new Label
    

Upvotes: 1

brakeroo
brakeroo

Reputation: 1467

I am assuming that the FillCalendarWindows() method is called from the UI thread but you don't want the UI thread to wait and freeze the control while the external operation GetResponseFromWebService.GetResponse takes place and that's why you are awaiting on that asynchronous call inside CallWebService().

I think a better way of using the async CallWebService is to use "async all the way" so you will have to change all the methods upstream of CallWebService into async methods. This has the added benefit that you won't need to do Device.BeginInvokeOnMainThread since async/await captures the synchronization context of the caller therefore the ChangeCalendar will be called on the UI thread.

async void SomeEventHandler()
{
// called from the UI thread (or its equivalent in Xamarin)
    await FillCalendarWindows();
}

protected async Task FillCalendarWindows()
    {
        try
        {
           //create 7*6 = 42 labels and buttons

            await CallWebService(StartDate.Month, StartDate.Year);

        }
        catch (Exception e)
        {

        }
    }

public async Task CallWebService(int Month, int Year)
    {
        try
        {
            await GetResponseFromWebService.GetResponse... ;

            // .... same code as in your example 

            ChangeCalendar(....);

        }
        catch /*... */
        {

        }
    } 

protected void ChangeCalendar(int changes)
    {
        try
        {
            /* no need to do Device.BeginInvokeOnMainThread () so you can replace all that with normal calls*/
        }
        catch (Exception e)
        {
            /* ... */
        }
    }

Not sure how the System.ArgumentOutOfRangeException gets to be raised, I was not able to find the right code version on github so I wasn't able to investigate that particular error. My guess is you have multiple threads modifying the "buttons" collection and when you're calling Device.BeginInvokeOnMainThread you might find the collection with less elements than expected.

TL;DR: use async/await all the way instead of calling async method in a sync manner, this should make it easier to find the cause of the problem

Upvotes: 2

Yuri S
Yuri S

Reputation: 5370

The question does not provide the full source code required to test the solution.

Answering your question about calling awaitable function in not async method. You can use

CallWebService().Wait(optional timeout); 

or

CallWebService().GetAwaiter().GetResult(); 

You should also change your function definition

async void CallWebService(int Month, int Year)

to

async Task CallWebService(int Month, int Year);

to correctly handle exceptions and thread switching

If you are ok to call CallWebService without blocking then you also can do

CallWebService(1,2).ContinueWith((task) =>
{

});

Upvotes: 3

Related Questions