从TimerCallback实例调用时,调用Dropbox API Client.Files.DownloadAsync不会返回元数据

时间:2019-11-12 18:42:14

标签: ios xamarin asynchronous dropbox-api dotnet-httpclient

我有一个跨平台的Xamarin.Forms移动项目,在该项目中,我尝试在启动时从Dropbox存储库下载文件。这是一个小于50kB的小型json文件。操作Dropbox API的代码在我的Android和iOS项目之间共享,并且我的Android实现按预期工作。这是一种Task方法,为方便起见,在这里我称其为downloader

更新:在iOS版本中,仅当直接从downloader调用BackgroundSynchronizer.Launch()的启动器(也是Task)时,我才能成功下载文件我唯一的AppDelegate的方法,但是在使用计时器通过downloader调用我的TimerCallback并定期调用EventHandler的计时器来委托此调用时,则不是这样。

我不知道为什么。

downloader

public class DropboxStorage : IDistantStoreService
{
    private string oAuthToken;
    private DropboxClientConfig clientConfig; 
    private Logger logger = new Logger
        (DependencyService.Get<ILoggingBackend>());

    public DropboxStorage()
    {
        var httpClient = new HttpClient(new NativeMessageHandler());
        clientConfig = new DropboxClientConfig
        {
            HttpClient = httpClient
        };
    }

    public async Task SetConnection()
    {
        await GetAccessToken();
    }

    public async Task<Stream> DownloadFile(string distantUri)
    {
        logger.Info("Dropbox downloader called.");
        try
        {
            await SetConnection();
            using var client = new DropboxClient(oAuthToken, clientConfig);
            var downloadArg = new DownloadArg(distantUri);
            var metadata = await client.Files.DownloadAsync(downloadArg);
            var stream = metadata?.GetContentAsStreamAsync();
            return await stream;
        }
        catch (Exception ex)
        {
            logger.Error(ex);
        }
        return null;
    }

已更新AppDelegate

using Foundation;
using UIKit;

namespace Izibio.iOS
{
    // The UIApplicationDelegate for the application. This class is responsible for launching the 
    // User Interface of the application, as well as listening (and optionally responding) to 
    // application events from iOS.
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {

        private BackgroundSynchronizer synchronizer = new BackgroundSynchronizer();
        //
        // This method is invoked when the application has loaded and is ready to run. In this 
        // method you should instantiate the window, load the UI into it and then make the window
        // visible.
        //
        // You have 17 seconds to return from this method, or iOS will terminate your application.
        //
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }

        public override void OnActivated(UIApplication uiApplication)
        {
            synchronizer.Launch();
            base.OnActivated(uiApplication);
        }

    }
}

编辑:中间类(嵌入了DownloadProducts函数):

public static class DropboxNetworkRequests
    {
        public static async Task DownloadProducts(IDistantStoreService distantStorage,
            IStoreService localStorage)
        {
            try
            {
                var productsFileName = Path.GetFileName(Globals.ProductsFile);
                var storeDirectory = $"/{Globals.StoreId}_products";
                var productsFileUri = Path.Combine(storeDirectory, productsFileName);
                var stream = await distantStorage.DownloadFile(productsFileUri);
                if (stream != null)
                {
                    await localStorage.Save(stream, productsFileUri);
                }
                else
                {
                    var logger = GetLogger();
                    logger.Info($"No file with the uri ’{productsFileUri}’ could " +
                        $"have been downloaded.");
                }
            }
            catch (Exception ex)
            {
                var logger = GetLogger();
                logger.Error(ex);
            }
        }

        private static Logger GetLogger()
        {
            var loggingBackend = DependencyService.Get<ILoggingBackend>();
            return new Logger(loggingBackend);
        }

    }

更新:失败的启动器类(Launch方法中的注释TriggerNetworkOperations(this, EventArgs.Empty); 成功下载了文件):

public class BackgroundSynchronizer
{
    private bool isDownloadRunning;
    private IDistantStoreService distantStorage;
    private IStoreService localStorage;
    private Timer timer;
    public event EventHandler SynchronizationRequested;

    public BackgroundSynchronizer()
    {
        Forms.Init();
        isDownloadRunning = false;
        distantStorage = DependencyService.Get<IDistantStoreService>();
        localStorage = DependencyService.Get<IStoreService>();
        Connectivity.ConnectivityChanged += TriggerNetworkOperations;
        SynchronizationRequested += TriggerNetworkOperations;
    }

    public void Launch()
    {
        try
        {
            var millisecondsInterval = Globals.AutoDownloadMillisecondsInterval;
            var callback = new TimerCallback(SynchronizationCallback);
            timer = new Timer(callback, this, 0, 0);
            timer.Change(0, millisecondsInterval);
            //TriggerNetworkOperations(this, EventArgs.Empty);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    protected virtual void OnSynchronizationRequested(object sender, EventArgs e)
    {
        SynchronizationRequested?.Invoke(sender, e);
    }

    private async void TriggerNetworkOperations(object sender, ConnectivityChangedEventArgs e)
    {
        if ((e.NetworkAccess == NetworkAccess.Internet) && !isDownloadRunning)
        {
            await DownloadProducts(sender);
        }
    }

    private async void TriggerNetworkOperations(object sender, EventArgs e)
    {
        if (!isDownloadRunning)
        {
            await DownloadProducts(sender);
        }
    }

    private void SynchronizationCallback(object state)
    {
        SynchronizationRequested(state, EventArgs.Empty);
    }

    private async Task DownloadProducts(object sender)
    {
        var instance = (BackgroundSynchronizer)sender;
        //Anti-reentrance assignments commented for debugging purposes
        //isDownloadRunning = true;
        await DropboxNetworkRequests.DownloadProducts(instance.distantStorage, instance.localStorage);
        //isDownloadRunning = false;
    }
}

我设置了一个日志文件来记录尝试下载时我的应用程序行为。

编辑:以下是从Launch方法直接调用TriggerNetworkOperations时收到的消息:

2019-11-12 19:31:57.1758|INFO|xamarinLogger|iZiBio Mobile Launched
2019-11-12 19:31:57.4875|INFO|persistenceLogger|Dropbox downloader called.
2019-11-12 19:31:58.4810|INFO|persistenceLogger|Writing /MAZEDI_products/assortiment.json at /Users/dev3/Library/Developer/CoreSimulator/Devices/5BABB56B-9B42-4653-9D3E-3C60CFFD50A8/data/Containers/Data/Application/D6C517E9-3446-4916-AD8D-565F4C206AF2/Library/assortiment.json

编辑:通过计时器及其回调启动时会得到的信息(出于调试目的,间隔为10秒)

2019-11-12 19:34:05.5166|INFO|xamarinLogger|iZiBio Mobile Launched
2019-11-12 19:34:05.8149|INFO|persistenceLogger|Dropbox downloader called.
2019-11-12 19:34:15.8083|INFO|persistenceLogger|Dropbox downloader called.
2019-11-12 19:34:25.8087|INFO|persistenceLogger|Dropbox downloader called.
2019-11-12 19:34:35.8089|INFO|persistenceLogger|Dropbox downloader called.

编辑:在第二种情况下,启动的任务事件最终被操作系统取消:

2019-11-13 09:36:29.7359|ERROR|persistenceLogger|System.Threading.Tasks.TaskCanceledException: A task was canceled.
  at ModernHttpClient.NativeMessageHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x002a5] in /Users/paul/code/paulcbetts/modernhttpclient/src/ModernHttpClient/iOS/NSUrlSessionHandler.cs:139 
  at System.Net.Http.HttpClient.SendAsyncWorker (System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken) [0x0009e] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/external/mono/mcs/class/System.Net.Http/System.Net.Http/HttpClient.cs:281 
  at Dropbox.Api.DropboxRequestHandler.RequestJsonString (System.String host, System.String routeName, System.String auth, Dropbox.Api.DropboxRequestHandler+RouteStyle routeStyle, System.String requestArg, System.IO.Stream body) [0x0030f] in <8d8475f2111a4ae5850a1c1349c08d28>:0 
  at Dropbox.Api.DropboxRequestHandler.RequestJsonStringWithRetry (System.String host, System.String routeName, System.String auth, Dropbox.Api.DropboxRequestHandler+RouteStyle routeStyle, System.String requestArg, System.IO.Stream body) [0x000f6] in <8d8475f2111a4ae5850a1c1349c08d28>:0 
  at Dropbox.Api.DropboxRequestHandler.Dropbox.Api.Stone.ITransport.SendDownloadRequestAsync[TRequest,TResponse,TError] (TRequest request, System.String host, System.String route, System.String auth, Dropbox.Api.Stone.IEncoder`1[T] requestEncoder, Dropbox.Api.Stone.IDecoder`1[T] resposneDecoder, Dropbox.Api.Stone.IDecoder`1[T] errorDecoder) [0x000a5] in <8d8475f2111a4ae5850a1c1349c08d28>:0 
  at Izibio.Persistence.DropboxStorage.DownloadFile (System.String distantUri) [0x00105] in /Users/dev3/Virtual Machines.localized/shared/TRACAVRAC/izibio-mobile/Izibio/Izibio.Persistence/Services/DropboxStorage.cs:44 
2019-11-13 09:36:29.7399|INFO|persistenceLogger|No file with the uri ’/******_products/assortiment.json’ could have been downloaded.

我将仅添加最后一个观察结果:从DownloadFile调试BackgroundSynchronizer任务时,我可以到达对client.Files.DowloadAsync的调用:var metadata = await client.Files.DownloadAsync(downloadArg);,但我赢了不会从此await语句中获取任何回报。

1 个答案:

答案 0 :(得分:0)

好的,我终于找到了一种解决方法,可以通过用iOS实现(NSTimer)替换.NET计时器。

我的BackgroundSynchronizer类新代码:

    public class BackgroundSynchronizer
    {
        private bool isDownloadRunning;
        private IDistantStoreService distantStorage;
        private IStoreService localStorage;
        private NSTimer timer;
        public event EventHandler SynchronizationRequested;

        public BackgroundSynchronizer()
        {
            Forms.Init();
            isDownloadRunning = false;
            distantStorage = DependencyService.Get<IDistantStoreService>();
            localStorage = DependencyService.Get<IStoreService>();
            Connectivity.ConnectivityChanged += TriggerNetworkOperations;
            SynchronizationRequested += TriggerNetworkOperations;
        }

        public void Launch()
        {
            try
            {
                var seconds = Globals.AutoDownloadMillisecondsInterval / 1000;
                var interval = new TimeSpan(0, 0, seconds);
                var callback = new Action<NSTimer>(SynchronizationCallback);
                StartTimer(interval, callback);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        protected virtual void OnSynchronizationRequested(object sender, EventArgs e)
        {
            SynchronizationRequested?.Invoke(sender, e);
        }

        private async void TriggerNetworkOperations(object sender, ConnectivityChangedEventArgs e)
        {
            if ((e.NetworkAccess == NetworkAccess.Internet) && !isDownloadRunning)
            {
                await DownloadProducts();
            }
        }

        private async void TriggerNetworkOperations(object sender, EventArgs e)
        {
            if (!isDownloadRunning)
            {
                await DownloadProducts();
            }
        }

        private void SynchronizationCallback(object state)
        {
            SynchronizationRequested(state, EventArgs.Empty);
        }

        private async Task DownloadProducts()
        {
            isDownloadRunning = true;
            await DropboxNetworkRequests.DownloadProducts(distantStorage, localStorage);
            isDownloadRunning = false;
        }

        private void StartTimer(TimeSpan interval, Action<NSTimer> callback)
        {
            timer = NSTimer.CreateRepeatingTimer(interval, callback);
            NSRunLoop.Main.AddTimer(timer, NSRunLoopMode.Common);   
        }
    }

哪个会产生以下记录行:

2019-11-13 14:00:58.2086|INFO|xamarinLogger|iZiBio Mobile Launched
2019-11-13 14:01:08.5378|INFO|persistenceLogger|Dropbox downloader called.
2019-11-13 14:01:09.5656|INFO|persistenceLogger|Writing /****_products/assortiment.json at /Users/dev3/Library/Developer/CoreSimulator/Devices/****/data/Containers/Data/Application/****/Library/assortiment.json
2019-11-13 14:01:18.5303|INFO|persistenceLogger|Dropbox downloader called.
2019-11-13 14:01:19.2375|INFO|persistenceLogger|Writing /****_products/assortiment.json at /Users/dev3/Library/Developer/CoreSimulator/Devices/****/data/Containers/Data/Application/****/Library/assortiment.json

但是我仍然对这两个计时器导致如此不同的行为的原因持开明的解释。