当从TimerCallback实例进行调用时,对Dropbox API Client.Files.DownloadAsy



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

更新:在iOS版本中,只有当直接从我唯一的AppDelegateBackgroundSynchronizer.Launch()方法调用我的downloader的启动器(这也是一个任务(时,我才能成功下载该文件,但当使用计时器通过TimerCallback来委派此调用时,则不能成功下载,CCD_7会定期调用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);
}
}
}

EDIT:中介类(嵌入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.

我将简单地添加最后一个观察:当从BackgroundSynchronizer调试DownloadFile任务时,我可以到达对client.Files.DowloadAsync:var metadata = await client.Files.DownloadAsync(downloadArg);的调用,但我不会从这个等待语句中检索到任何返回。

好吧,我终于找到了解决这个问题的方法,用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

但我仍然愿意对两个计时器导致如此不同行为的原因进行深入的解释。

最新更新