簡體   English   中英

在C#的非異步構造函數中調用其他頁面觸發異步Function

[英]Trigger Async Function on Other Pages by Calling it in Non-Async Constructor in C#

在我的應用程序中,有一個 Async Function ProcessOffer() 當我在 Constructor 中將它稱為ProcessOffer時,它會同步工作。 我想在構造函數中異步調用這個 Function 。

ProcessOffer()是在CredentialViewModel中實現的 function ,但我希望它應該在應用程序( IndexViewModel等)的任何地方異步觸發。

如果我在IndexPage上,並且 Web 應用程序向移動應用程序發送請求,應該觸發ProcessOffer() .. 實際上ProcessOffer所做的是,它要求用戶輸入 PIN,如果它是正確的,它會發回對 Web 申請的回應。

我試過其他帖子的答案,但他們返回了錯誤Autofac.Core.DependencyResolutionException: 'An exception was thrown while activating App.Name ,當我從 Web 應用程序向移動應用程序發送請求時。

我試過的解決方案。

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", null, 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,
            INavigationService navigationService,
            IConnectionService connectionService,
            IMessageService messageService,
            IAgentProvider agentContextProvider,
            IEventAggregator eventAggregator,
            ILifetimeScope scope
            ) : base(
                "Index",
                userDialogs,
                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, null, 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
    }
}

應用程序.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,
                                path2: ".indy_client",
                                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;
    }
}

看,您在IndexViewModel class 中有public override async Task InitializeAsync(object navigationData)方法。我想框架會調用它來初始化視圖 model。因此, 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();
    }

無論如何,您必須避免在構造函數中調用異步操作。

正如您之前已經問過的那樣,在 class 的構造函數中啟動異步代碼可能是一堆蠕蟲。 您應該考慮執行以下任一操作:

  1. 改為在生命周期方法中啟動ProcessOffer 例如,當視圖顯示調用ProcessOfferCommand時。
  2. 使用 fire 並忘記不阻塞構造函數: Task.Run(ProcessOffer)你應該避免這種情況。
  3. 使用像NotifyTask Stephen Cleary 在這里描述的東西: https://learn.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
  4. 使用帶有私有構造函數的CreateAsync模式。 但是,這通常不適用於依賴注入。 在解析期間做 IO 密集工作並不是真正正確的地方。

在我看來,使用 1. 或 3. 將是最好的解決方案,也許傾向於 1. 的組合。使用 NotifyTask 可以在加載完成時通知您。

所以在我的視圖模型中,如果我需要異步初始化,我通常將 OnAppearing() 傳遞給視圖 model 而不是構造函數。 有人可以告訴我這是否不明智,但它解決了我的需求。

代碼隱藏視圖:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CredentialView : ContentPage
{
    public CredentialView()
    {
        InitializeComponent();
        BindingContext = new CredentialViewModel();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        ((CredentialViewModel)BindingContext).OnAppearing();
    }
}

查看 Model:

public class CredentialViewModel : INotifyPropertyChanged
{

    public CredentialViewModel ()
    {
    }

    public async void OnAppearing()
    {
        await ProcessOffer();
    }

 }

僅供參考,我在 xamarin 上使用異步代碼遇到的最大問題是使用 BeginInvokeOnMainThread。 我的理解是 touched UI 應該在主線程上執行。 (可能是為什么他們稱它為 UI 線程:哈哈)示例:

Device.BeginInvokeOnMainThread(() =>
{
    observableCollection.Clear();
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM