ExpertGenie
ExpertGenie

Reputation: 195

Async Method not running properly in service mode in Xamarin

I have the following service defined. The service is supposed to run when the device is booted up. However, the service is successfully started, but it seems on of the method within the DoBackgroundJob() method does not get invoked. When I launch the service from the MainActivity and put breakpoints, the all the methods are executed correctly and I get the desired result. Could someone please advise what is wrong here ?

I have wrapped the DoBackgroundJob() in a new thread so that the service does not block the UI of the application if it is launched manually. The application is supposed to work in two separate processes.

What I am wondering, is whether an async method like DoBackgroundJob() should contain all the methods within it async ? Please help me to solve this issue, as I suspect some methods is not having sufficient to be invoked properly.

    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        try
        {

            Thread t = new Thread(() =>
            {
                dsa = new DataServiceAccess();
                Task.Run(async () =>
                {                     
                    await DoBackgroundJob();
                });
            });

            t.Start();

        }
        catch (Exception ex)
        {

        }

        base.OnStartCommand(intent, flags, startId);
        return StartCommandResult.Sticky;

    }

    public void GetGps()
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            try
            {
                LocationService locService = new LocationService();
                GpsData = locService.GetLocation();
                a = GpsData.Latitude;
                b = GpsData.Longitude;
            }
            catch (Exception ex)
            {

            }
        });        
    }

    public async Task DoBackgroundJob()
    {
        GetGps();

        try
        {

            ObservableCollection<Trucks> Trucks = new ObservableCollection<Trucks>();
            Trucks truck = new Trucks();
            TrucksService ds = new TrucksService();

            beacon.DetectAvailableTrucks();

            await Task.Delay(100000);

            Trucks = beacon.GetAvailableTrcuks();

            await MatchRequiredTrayBeacon(Truckss);


            await Task.Delay(8000);
            await DoBackgroundJob();


        } catch (Exception e)
        {

        }
        finally
        {


        }
    }


    private ObservableCollection<TrucksLoaded> ConstructTrucks(ObservableCollection<Trucks> trucksMatch)
    {
        string address = "";

        List<ConfigDataModel> conf = new List<ConfigDataModel>();
        conf =  dsa.GetAllConfigData();

        foreach (var item in conf)
        {
            address = item.address;
        }

        ObservableCollection<TrucksLoaded> TrucksLoadedFound = new ObservableCollection<TrucksLoaded>();

        foreach (var item in matchedBeacon)
        {

            TrucksLoadedFound.Add(new TrucksLoaded(item.Id, item.Name, address, latitude,
            longitude, DateTime.Now.ToString(), false, item.BatteryLife));

        }

        return TrucksLoadedFound;

    }


    public ObservableCollection<TrucksLoaded> MatchTrucks(ObservableCollection<Trucks> foundTrucks)
    {
        TrucksLoaded = new ObservableCollection<TrucksLoaded>();

        foundTrucks = new ObservableCollection<Trucks>();

        foundTrucks = HandleTrucks(foundTrucks);

        TrucksLoaded = ConstructTrucks(foundTrucks);

        SavetoLocalDB(TrucksLoaded);

        return TrucksLoaded;
    }

    private ObservableCollection<Trucks> CheckMatching(ObservableCollection<Trucks> foundTrucks)
    {
          MatchedTrucks = new ObservableCollection<Trucks>();

          // matching logic implemeted here.. 

     return MatchedTrucks;

    }

    private void SavetoLocalDB(ObservableCollection<TrucksLoaded> TrucksLoaded)
    { 
        foreach (var item in TrucksLoaded)
        {
            dsa.AddTrucksLoaded(item).ConfigureAwait(false);
        }
    }

Partial Code of Location Service that requires to be run on a main thread

 public void InitLocation()
    {
        if (_locationManager.AllProviders.Contains(LocationManager.NetworkProvider) && _locationManager.IsProviderEnabled(LocationManager.NetworkProvider))
        {
            _locationManager.RequestLocationUpdates(LocationManager.NetworkProvider, 2000, 5, this);
        }

Upvotes: 0

Views: 970

Answers (1)

SushiHangover
SushiHangover

Reputation: 74194

You can not use Xamarin.Forms calls within the Service.

  • Calling Device.BeginInvokeOnMainThread will cause your Service to be killed when your service receives the Intent.ActionBootCompleted as you do not have a UI and an inited Xamarin.Forms app... Since you have the service returning StartCommandResult.Sticky the OS will will continually try to restart your service (on an ever decreasing schedule).

You do not need the double thread in the OnStartCommand, just one will do it.

  • Only start your worker thread once as OnStartCommand can get called multiple times...

I do not know what your LocationService method consists of, but again make sure that you are not using Xamarin.Forms calls in it.

  • The Android location services, or preferably the Google Fused Location services, will work fine without any Forms calls.

Test your Service startup on ActionBootCompleted by using adb, depending upon the device/emulator type/API/etc... you can use one of these adb cmds:

  • adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -p UseYourPackageNameHere
  • adb shell am broadcast -a android.intent.action.ACTION_BOOT_COMPLETED
  • adb reboot

Example:

Add your boot permissions (manually in the manifest or via an attribute)

[assembly: UsesPermission(Manifest.Permission.ReceiveBootCompleted)]

Service code:

[Service]
public class BootService : Service
{
    const string TAG = "SushiHangover";
    public const string SERVICE = "com.sushihangover.BootService";
    Thread serviceThread;

    public IBinder Binder { get; private set; }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        Log.Debug(TAG, $"OnStartCommand : {Thread.CurrentThread.ManagedThreadId}");

        serviceThread = serviceThread ?? new Thread(ServiceRun); // Many ways... use a Task with a cancellation token, etc... 
        if (!serviceThread.IsAlive)
            try
            {
                serviceThread.Start();
            }
            catch (ThreadStateException ex) when (ex.Message == "Thread has already been started.")
            {
                // Xamarin.Android bug: isAlive always returns false, so eat the Start() exception if needed
            }

        base.OnStartCommand(intent, flags, startId);
        return StartCommandResult.Sticky;
    }

    public override void OnTrimMemory(TrimMemory level)
    {
        // Stop serviceThread? free resources? ...
        base.OnTrimMemory(level);
    }

    public override void OnLowMemory()
    {
        // Stop serviceThread? free resources? ...
        base.OnLowMemory();
    }

    public override void OnDestroy()
    {
        serviceThread.Abort(); // Handle ThreadAbortException in your thread, cleanup resources if needed...
        base.OnDestroy();
    }

    public override IBinder OnBind(Intent intent)
    {
        Log.Debug(TAG, $"OnBind");
        Binder = new BootBinder(this);
        return Binder;
    }

    public class BootBinder : Binder
    {
        public BootBinder(BootService service)
        {
            Service = service;
        }

        public BootService Service { get; private set; }
    }

    async void ServiceRun()
    {
        int i = 0;
        while (true) // Handle cancellations...
        {
            await Task.Delay(1000);
            Log.Debug(TAG, $"{i} : {Thread.CurrentThread.ManagedThreadId}");
            i++;
        }
    }
}

BroadcastReceiver code:

[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { Intent.ActionBootCompleted })]    
public class BootBroadcastReceiver : BroadcastReceiver
{
    const string TAG = "SushiHangover";

    public override void OnReceive(Context context, Intent intent)
    {
        Log.Debug(TAG, $"OnReceive");
        var serviceIntent = new Intent(context, typeof(BootService));
        context.StartService(serviceIntent);
    }
}

Test

Install the app, via a debugging session is fine, as long as you have started the app at least once.

Now trigger the ActionBootCompleted intent via the adb cmd above.

Monitor logcat and you should see (I issued multiple ActionBootCompleted):

 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 0 : 6
 D SushiHangover: 1 : 7
 D SushiHangover: 2 : 6
 D SushiHangover: 3 : 7
 D SushiHangover: 4 : 6
 D SushiHangover: 5 : 7
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 6 : 6
 D SushiHangover: 7 : 7
 D SushiHangover: 8 : 6
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 9 : 7
 D SushiHangover: 10 : 6
 D SushiHangover: OnReceive
 D SushiHangover: OnStartCommand : 1
 D SushiHangover: 11 : 7
 D SushiHangover: 12 : 6
 D SushiHangover: 13 : 7
 D SushiHangover: 14 : 6
 D SushiHangover: 15 : 6
 D SushiHangover: 16 : 7
 D SushiHangover: 17 : 6
 D SushiHangover: 18 : 7

Upvotes: 2

Related Questions