问题描述
在我的应用程序中,有一个异步功能ProcessOffer()
。当我在构造函数中将其称为ProcessOffer
时,它可以同步工作。我想在构造函数中异步调用此函数。
ProcessOffer()
是在Credentialviewmodel
中实现的功能,但我希望这样做,应该在应用程序的任何地方(Indexviewmodel
e.t.c)异步触发。
如果我在IndexPage
上,并且Web应用程序向移动应用程序发送了一个请求ProcessOffer()
,则应该触发该操作。实际上,ProcessOffer
所做的是,它要求用户输入PIN码(如果正确),它会将响应发送回Web应用程序。
我尝试了其他帖子的答案,但是当我从Web应用程序向移动应用发送请求时,它们返回了错误Autofac.Core.DependencyResolutionException: 'An exception was thrown while activating App.Name
。
我尝试过的解决方案。
1- https://stackoverflow.com/a/64012442/14139029
2- Task.Run(() => ProcessOffer()).Wait();
3- ProcessOffer().GetAwatier().GetResult();
Credentialviewmodel.cs
namespace Osma.Mobile.App.viewmodels.Credentials
{
public class Credentialviewmodel : ABaseviewmodel
{
private readonly CredentialRecord _credential;
private readonly ICredentialService _credentialService;
private readonly IAgentProvider _agentContextProvider;
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IPoolConfigurator _poolConfigurator;
[Obsolete]
public Credentialviewmodel(
IUserDialogs userDialogs,INavigationService navigationService,ICredentialService credentialService,IAgentProvider agentContextProvider,IConnectionService connectionService,IMessageService messageService,IPoolConfigurator poolConfigurator,CredentialRecord credential
) : base(
nameof(Credentialviewmodel),userDialogs,navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
if (_credentialState == "Offered")
{
ProcessOffer();
}
}
[Obsolete]
public async Task ProcessOffer()
{
foreach (var item in _credential.CredentialAttributesValues)
{
await SecureStorage.SetAsync(item.Name.ToString(),item.Value.ToString());
}
var RegisteredPIN = await SecureStorage.GetAsync("RegisteredPIN");
string PIN = await App.Current.MainPage.displayPromptAsync("Enter PIN",null,"Ok","Cancel",6,Keyboard.Numeric);
if (PIN == RegisteredPIN)
{
try
{
//await _poolConfigurator.ConfigurePoolsAsync();
var agentContext = await _agentContextProvider.GetContextAsync();
var credentialRecord = await _credentialService.GetAsync(agentContext,_credential.Id);
var connectionId = credentialRecord.ConnectionId;
var connectionRecord = await _connectionService.GetAsync(agentContext,connectionId);
(var request,_) = await _credentialService.CreateRequestAsync(agentContext,_credential.Id);
await _messageService.SendAsync(agentContext.Wallet,request,connectionRecord);
await DialogService.AlertAsync("Request has been sent to the issuer.","Success","Ok");
}
catch (Exception e)
{
await DialogService.AlertAsync(e.Message,"Error","Ok");
}
}
else if (PIN != RegisteredPIN && PIN != null)
{
DialogService.Alert("Provided PIN is not correct");
}
}
#region Bindable Command
[Obsolete]
public ICommand ProcessOfferCommand => new Command(async () => await ProcessOffer());
public ICommand NavigateBackCommand => new Command(async () =>
{
await NavigationService.PopModalAsync();
});
#endregion
#region Bindable Properties
private string _credentialState;
public string CredentialState
{
get => _credentialState;
set => this.RaiseAndSetIfChanged(ref _credentialState,value);
}
#endregion
}
}
Indexviewmodel.cs
namespace Osma.Mobile.App.viewmodels.Index
{
public class Indexviewmodel : ABaseviewmodel
{
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IAgentProvider _agentContextProvider;
private readonly IEventAggregator _eventAggregator;
private readonly ILifetimeScope _scope;
public Indexviewmodel(
IUserDialogs userDialogs,IEventAggregator eventAggregator,ILifetimeScope scope
) : base(
"Index",navigationService
)
{
_connectionService = connectionService;
_messageService = messageService;
_agentContextProvider = agentContextProvider;
_eventAggregator = eventAggregator;
_scope = scope;
}
public override async Task InitializeAsync(object navigationData)
{
await base.InitializeAsync(navigationData);
}
public class Post
{
public string Success { get; set; }
public string firstname { get; set; }
}
[Obsolete]
public async Task ScanVerification(object sender,EventArgs e)
{
// Code
}
public async Task SettingsPage(Settingsviewmodel settings) => await NavigationService.NavigatetoAsync(settings,NavigationType.Modal);
#region Bindable Command
public ICommand SettingsPageCommand => new Command<Settingsviewmodel>(async (settings) =>
{
await SettingsPage(settings);
});
[Obsolete]
public ICommand ScanVerificationCommand => new Command(async () => await ScanVerification(default,default));
#endregion
}
}
App.xml.cs
[assembly: XamlCompilation(XamlCompilationoptions.Compile)]
namespace Osma.Mobile.App
{
public partial class App : Application
{
public new static App Current => Application.Current as App;
public static IContainer Container { get; set; }
// Timer to check new messages in the configured mediator agent every 10sec
private readonly Timer timer;
private static IHost Host { get; set; }
public App()
{
InitializeComponent();
timer = new Timer
{
Enabled = false,AutoReset = true,Interval = TimeSpan.FromSeconds(10).TotalMilliseconds
};
timer.Elapsed += Timer_Elapsed;
}
public App(IHost host) : this() => Host = host;
public static IHostBuilder BuildHost(Assembly platformSpecific = null) =>
XamarinHost.CreateDefaultBuilder<App>()
.ConfigureServices((_,services) =>
{
services.AddAriesFramework(builder => builder.RegisterEdgeAgent(
options: options =>
{
options.AgentName = "Mobile Holder";
options.EndpointUri = "http://11.222.333.44:5000";
options.WalletConfiguration.StorageConfiguration =
new WalletConfiguration.WalletStorageConfiguration
{
Path = Path.Combine(
path1: FileSystem.AppDataDirectory,path2: ".indy_client",path3: "wallets")
};
options.WalletConfiguration.Id = "MobileWallet";
options.WalletCredentials.Key = "SecretWalletKey";
options.RevocationRegistryDirectory = Path.Combine(
path1: FileSystem.AppDataDirectory,path3: "tails");
// Available network configurations (see PoolConfigurator.cs):
options.PoolName = "sovrin-test";
},delayProvisioning: true));
services.AddSingleton<IPoolConfigurator,PoolConfigurator>();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterassemblyModules(typeof(CoreModule).Assembly);
if (platformSpecific != null)
{
containerBuilder.RegisterassemblyModules(platformSpecific);
}
containerBuilder.Populate(services);
Container = containerBuilder.Build();
});
protected override async void OnStart()
{
await Host.StartAsync();
// View models and pages mappings
var _navigationService = Container.Resolve<INavigationService>();
_navigationService.AddPageviewmodelBinding<Mainviewmodel,MainPage>();
_navigationService.AddPageviewmodelBinding<Registerviewmodel,RegisterPage>();
_navigationService.AddPageviewmodelBinding<Indexviewmodel,IndexPage>();
_navigationService.AddPageviewmodelBinding<Settingsviewmodel,SettingsPage>();
_navigationService.AddPageviewmodelBinding<Credentialsviewmodel,CredentialsPage>();
_navigationService.AddPageviewmodelBinding<Credentialviewmodel,CredentialPage>();
if (Preferences.Get(AppConstant.LocalWalletProvisioned,false))
{
await _navigationService.NavigatetoAsync<Mainviewmodel>();
}
else
{
await _navigationService.NavigatetoAsync<Providerviewmodel>();
}
timer.Enabled = true;
}
private void Timer_Elapsed(object sender,ElapsedEventArgs e)
{
// Check for new messages with the mediator agent if successfully provisioned
if (Preferences.Get(AppConstant.LocalWalletProvisioned,false))
{
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var context = await Container.Resolve<IAgentProvider>().GetContextAsync();
await Container.Resolve<IEdgeClientService>().FetchInBoxAsync(context);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
});
}
}
protected override void OnSleep() =>
// Stop timer when application goes to background
timer.Enabled = false;
protected override void OnResume() =>
// Resume timer when application comes in foreground
timer.Enabled = true;
}
}
解决方法
看,您在public override async Task InitializeAsync(object navigationData)
类中拥有IndexViewModel
方法。我想框架调用它来初始化视图模型。因此,无论如何CredentialViewModel
都继承了相同的ABaseViewModel
,为什么不只覆盖InitializeAsync
中的CredentialViewModel
并从中调用ProcessOffer
?
public CredentialViewModel(
IUserDialogs userDialogs,INavigationService navigationService,ICredentialService credentialService,IAgentProvider agentContextProvider,IConnectionService connectionService,IMessageService messageService,IPoolConfigurator poolConfigurator,CredentialRecord credential
) : base(
nameof(CredentialViewModel),userDialogs,navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
}
public override async Task InitializeAsync(object navigationData)
{
if (_credentialState != "Offered") return;
await ProcessOffer();
}
无论如何,您必须避免在构造函数中调用异步操作。
,正如您之前已经问过的,在类的构造函数中启动异步代码可能是蠕虫病毒。相反,您应该考虑这样做:
- 改为使用生命周期方法启动
ProcessOffer
。例如,当视图显示时,调用ProcessOfferCommand
。 - 使用火并且忘记不要阻塞构造函数:
Task.Run(ProcessOffer)
您可能应该避免这种情况。 - 使用类似
NotifyTask
的内容,Stephen Cleary在这里描述:https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/march/async-programming-patterns-for-asynchronous-mvvm-applications-data-binding,您可以在此处找到完整的代码:https://github.com/StephenCleary/Mvvm.Async/blob/master/src/Nito.Mvvm.Async/NotifyTask.cs - 对私有构造函数使用
CreateAsync
模式。但是,这通常不适用于依赖注入。在解决过程中进行IO密集型工作实际上并不是正确的选择。
我认为使用1.或3.是最好的解决方案,也许倾向于1.使用NotifyTask的组合,该工具可以在加载完成时通知您。
,因此,在我的视图模型中,如果我需要异步初始化,我通常将OnAppearing()传递给视图模型而不是构造函数。有人可以告诉我这是否不明智,但这解决了我的需求。
视图后面的代码:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CredentialView : ContentPage
{
public CredentialView()
{
InitializeComponent();
BindingContext = new CredentialViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
((CredentialViewModel)BindingContext).OnAppearing();
}
}
查看模型:
public class CredentialViewModel : INotifyPropertyChanged
{
public CredentialViewModel ()
{
}
public async void OnAppearing()
{
await ProcessOffer();
}
}
也仅供参考,我在xamarin上使用异步代码所遇到的最大难题是使用BeginInvokeOnMainThread。据我了解,触摸过的UI应该在主线程上执行! (可能是为什么他们将其称为UI线程。哈哈) 示例:
Device.BeginInvokeOnMainThread(() =>
{
observableCollection.Clear();
});