简体   繁体   中英

Marshalling data back to UI Thread from within Async Action

I've got an ICommand that needs to set data to a property on the UI Thread.

public override async void Execute(object parameter)
{
    var vm = (MyVm)parameter;
    var data = await _myDataService.GetData();
    vm.MyData = data; // must be set on UI Thread due to binding.
}

Now I want to wrap my call in an event logger (I originally wanted to do AOP and decorate the method with a logging attribute, but I couldn't figure it out in a PCL). So I moved onto wrapping my call like this.

public override void Execute(object parameter)
{
    EventLogger.LogEvent(this,
        EventLogEntryType.Command,
        EventLogErrorSeverity.Warning,
        Errors.GetServiceAreaCommand_ErrorMessage,
        async () =>
        {
            var vm = (MyVm)parameter;
            var data = await _myDataService.GetData();
            vm.MyData = data; // must be set on UI Thread due to binding.
        });
}

Here's the LogEvent method.

public static void LogEvent(object sender,
    EventLogEntryType entryType,
    EventLogErrorSeverity eventLogErrorSeverity,
    string friendlyErrorMessage,
    Action action)
{
    var name = sender.GetType().Name.SplitCamelCase();
    var startEntry = new EventLogEntry(entryType);
    LogEvent(string.Format("Start: {0}", name), startEntry);

    try
    {
        action.Invoke();
    }
    catch (Exception ex)
    {
        var exEntry = new EventLogEntry(EventLogEntryType.Error, friendlyErrorMessage, false, ex)
        {
            ErrorSeverity = eventLogErrorSeverity
        };
        LogEvent(string.Format("Error: {0}", name), exEntry);
        if (eventLogErrorSeverity == EventLogErrorSeverity.Critical)
        {
            throw;
        }
    }
    var endEntry = new EventLogEntry(entryType);
    LogEvent(string.Format("Finish: {0}", name), endEntry);
}

The problem is that it appears as though I'm STILL setting the property on a background thread instead of the Main thread (IllegalStateException in Android).

What is the cleanest way to set the data as is being done in the first example, while still wrapping the Action in a logging method?


I also had success creating a base class for ICommand , but it A) changed the method signatures for CanExecute and Execute , and B) it also (obviously) doesn't extend it's capabilities beyond Commands .

I'm looking for a clean way to log methods (BeforeExecute, AfterExecute, OnError) no matter what they do.


As an aside, the ideal logging mechanism would be to use an Interceptor, but I'm just not strong enough in my C# chops to implement it.

[Log(EventLogEntryType.Command, EventLogErrorSeverity.Warning, "Some Friendly Message")]
public override async void Execute(object parameter)
{
    var vm = (MyVm)parameter;
    var data = await _myDataService.GetData();
    vm.MyData = data; // must be set on UI Thread due to binding.
}

If you have (caveat below) access to the Activity object in your code then you can probably do;

Activity.RunOnUiThread(() => {
    //Execute my code on UIThread here
});

But it's an if , because I note you're using a PCL, or have referenced using one, so I suspect that a shared library is not going to know anything about the Activity (unless you pass that too). Very much depends on your app structure and where this code is, but within the main Xamarin.Android project where your views are the above should work

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM