2015-07-14 2 views
4

Я довольно за кривой с асинхронным ожиданием, так что это, вероятно, вопрос «duh».Как выполнить асинхронный запуск в приложении WPF?

Я работаю над тем, что должно быть очень минимальным пользовательским интерфейсом, которое запускается с панели задач с помощью библиотеки WPF NotifyIcon.

Приложение должно работать очень просто (для пользователя) следующим образом:

  • Программа начинается
  • При необходимости, есть заставка сообщает пользователю программа запущена и побуждая их для входа (если они еще не сделали этого в предыдущей итерации).
  • WPF NotifyIcon появляется на панели задач.
  • исполнение
  • Асинхронный начинается

Проблема, в которую я бегу является «Асинхронное выполнение начинается» часть. Все, что происходит до тех пор, работает нормально, но когда программа начинает «запускать», пользовательский интерфейс блокируется (под которым я имею в виду, пользователи могут щелкнуть, как безумные люди, на значке Tray, и контекстное меню не появится).

Эта блокировка происходит в течение недопустимо длительного периода времени.

Это код запуска:

private async void AppStartup(object sender, StartupEventArgs e) { 
    this.TRSIcon = this.FindResource("TRSIcon") as TaskbarIcon; 
    if (Settings.Default.DoUpgrade) { //Upgrade if necessary. 
     Settings.Default.Upgrade(); 
     Settings.Default.DoUpgrade = false; 
     Settings.Default.Save(); 
    } 

    if (string.IsNullOrEmpty(Settings.Default.Username) || string.IsNullOrEmpty(Settings.Default.Password)) { 
     new Help().ShowDialog(); 
     Tuple<string, string> UP; 
     if ((UP = Login.Instance.GetUserPassword()) != null) { 
      Settings.Default.Username = UP.Item1; 
      Settings.Default.Password = UP.Item2; 
      Settings.Default.Save(); 
     } else 
      return; 
    } 
    await this.Start(); //<-----This is where the meat of the program runs and it hangs the UI until it finishes. 
    return; //<-----This is just so that I have a break point to see that await this.Start is blocking (I have to do it like that right? or do I?) 
} 

Это Resources.xaml:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Tools="clr-namespace:WPFTools.TaskbarNotification;assembly=WPFTools" 
    xmlns:TR="clr-namespace:TriviaRetriever"> 
    <ContextMenu x:Key="TSRInterfaceMenu" x:Shared="false"> 
     <MenuItem Header="Login" Command="{Binding cmdLogin}"/> 
     <MenuItem Header="Get My Trivia" Command="{Binding cmdDownload}"/> 
     <MenuItem Header="Register" Command="{Binding cmdRegister}"/> 
     <MenuItem Header="Lost Password" Command="{Binding cmdLostPassword}"/> 
     <MenuItem Header="About" Command="{Binding cmdAbout}"/> 
     <MenuItem Header="Log Out" Command="{Binding cmdLogout}"/> 
     <MenuItem Header="Exit" Command="{Binding cmdExit}"/> 
    </ContextMenu> 

    <Tools:TaskbarIcon 
     x:Key="TRSIcon" 
     MenuActivation="LeftOrDoubleClick" 
     IconSource="/TRIcon.ico" 
     DoubleClickCommand="{Binding cmdAbout}" 
     ContextMenu="{StaticResource TSRInterfaceMenu}"> 
     <Tools:TaskbarIcon.DataContext> 
      <TR:TRSIViewModel/> 
     </Tools:TaskbarIcon.DataContext> 
    </Tools:TaskbarIcon> 
</ResourceDictionary> 

Это MVVM для контекстного меню команд:

public class TRSIViewModel { 
    public ICommand cmdLogin { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => (Application.Current as App).Core == null, 
       actCommand = async () => { 
        Tuple<string, string> LoginPassword = Login.Instance.GetUserPassword(); 
        if (LoginPassword != null) { 
         Settings.Default.Username = LoginPassword.Item1; 
         Settings.Default.Password = LoginPassword.Item2; 
         Settings.Default.Save(); 
         await (Application.Current as App).Start(); 
        } 
       } 
      }; 
     } 
    } 

    public ICommand cmdLogout { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => (Application.Current as App).Core != null, 
       actCommand = () => { 
        (Application.Current as App).Core.Terminate(); 
        (Application.Current as App).Core = null; 
       } 
      }; 
     } 
    } 

    public ICommand cmdRegister { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => true, 
       actCommand = () => Process.Start(@"https://www.digigames.com/weekly_subscriptions/index.php") 
      }; 
     } 
    } 

    public ICommand cmdLostPassword { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => true, 
       actCommand = () => Process.Start(@"https://www.digigames.com/weekly_subscriptions/lost_password.php") 
      }; 
     } 
    } 

    public ICommand cmdAbout { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => true, 
       actCommand = () => (Application.Current as App).TRSIcon.ShowCustomBalloon(new About(), PopupAnimation.Slide, 5000) 
      }; 
     } 
    } 

    public ICommand cmdExit { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => true, 
       actCommand = () => { 
        if ((Application.Current as App).Core != null) 
         (Application.Current as App).Core.Terminate(); 
        Application.Current.Shutdown(0); 
       } 
      }; 
     } 
    } 

    public ICommand cmdDownload { 
     get { 
      return new DelegateCommand { 
       fncCanExecute = () => (Application.Current as App).Core != null, 
       actCommand = async () => await (Application.Current as App).Core.DownloadTrivia(true) 
      }; 
     } 
    } 

    public class DelegateCommand : ICommand { 

     public Action actCommand { get; set; } 
     public Func<bool> fncCanExecute { get; set; } 

     public bool CanExecute(object parameter) { 
      return this.fncCanExecute != null && this.fncCanExecute(); 
     } 

     public event EventHandler CanExecuteChanged { 
      add { CommandManager.RequerySuggested += value; } 
      remove { CommandManager.RequerySuggested -= value; } 
     } 

     public void Execute(object parameter) { this.actCommand(); } 
    } 
} 

Что я делаю Неправильно здесь?

+0

Вы должны вернуть «Задачу», вы хотите избежать использования «async void» в качестве возвращаемого типа. См. Https://msdn.microsoft.com/en-us/magazine/jj991977.aspx –

+0

@Will: Вероятно, проблема связана с методом 'Start', который не отображается. Пожалуйста, упростите минимальный, воспроизводимый пример - определите синхронный асинхронный код и просто спросите об этом. –

+0

@RonBeyer Возврат задачи действителен, за исключением, или, как я слышал, при работе с обработчиками событий, которые являются методом запуска. – Will

ответ

2

Я думаю, что ваш вопрос все о вашем методе Start.

Но сначала сначала. Ваша точка останова не делает то, что вы ожидаете от нее.Он прерывается после того, как фактически закончен метод Start, а оставшаяся функция выполняется, а не раз, когда поток пользовательского интерфейса высвобождается снова. Вы должны понимать, что поток пользовательского интерфейса можно запустить снова, как только выполнение оставит синхронизацию пользовательского интерфейса в функции Start.

Хороший способ узнать, сколько времени потребуется до тех пор, пока метод фактически не освободит выполнение, чтобы ждать, пока он вернет объект Task.

var pendingTask = this.Start(); 
Debugger.Break(); 
await pendingTask; 

В Task объект возвращается после того, как метод Start попадает в функцию, которая асинхронно внутри. await возвращается после завершения pendingTask.

В вашем случае я думаю, что времена будут похожи, потому что метод Start не отправляет достаточную работу в фоновом режиме.

Существует несколько способов сделать это. Если ваш метод Start не взаимодействует с пользовательским интерфейсом, вы в порядке. Вы просто отправляете весь метод в фоновом режиме и выполняете его. Это работает так же просто, как:

await Task.Run(() => this.Start()); 

Это посылает задание в резьбе ThreadPool и освобождает пользовательский интерфейс снова сразу же. Метод Task.Run имеет перегрузку, которая автоматически разворачивает внутренний Task, который возвращается методом Start.

Если ваш метод взаимодействует с пользовательским интерфейсом, вам необходимо изменить метод внутри. Ищите детали внутри метода, которые занимают много времени и не взаимодействуют с пользовательским интерфейсом и завершают их в вызовы метода Task.Run, как показано выше.

Каждый await установит SynchronizationContext, который присутствовал раньше. Таким образом, каждый await в потоке, который может изменить пользовательский интерфейс, гарантирует, что продолжение выполняется и в этом же потоке.

Так что такие вещи, как эта работа без проблем:

someLabel.Label = "Working…"; 
await Task.Run(() => DoManyThings()); 
someLabel.Label = "Done! :D" 

Я надеюсь, что помогает. Я не могу дать вам больше советов, не зная, что делает ваш метод Start. Но я надеюсь, что ответ подскажет вам правильный путь.

Моя обычная оговорка: Обычно я использую VB.net, поэтому мой код на C# может быть испорчен в вопросах синтаксиса. Если вы заметили какую-либо ошибку, не стесняйтесь ее редактировать или сказать, что не так.

+0

Вы были правы; основная часть проблемы была в методе «старт», но самой большой проблемой было создание объекта пользовательского интерфейса перед запуском всего отстающего материала, когда было лучше просто создать его, когда все было запущено асинхронно с помощью вызова Dispatcher.BeginInvoke для этого. . Спасибо за помощь. – Will

0

Просто создайте новый Thread и отпустите его.

private void App_OnStartup(object sender, StartupEventArgs e) 
    { 
     //Some login logic 
     if(loggedIn) //Or however you do it 
     { 
      Thread thread = new Thread(() => 
      { 
       this.RunWhateverMethodWillDoABunchOfStuff() 
      }); 
      thread.SetApartmentState(ApartmentState.STA); 
      thread.Start(); 
     } 

В зависимости от конкретной программы и то, что вы хотите, вы можете также использовать BackgroundWorker, которая является хорошим ansync способом запуска Backgroun операций при ПОЛУЧАТЬ обновление прогресса от backgrund потока, что позволяет обновлять нечто вроде загрузочный бар или что-то в этом роде. Если вы хотите получить дополнительную информацию об этом, сообщите мне

EDIT: извините, я не заметил, что вы использовали метод ansync. Я бы избегал этого вместе и просто запускал его в обычном методе и просто в потоке (предполагая, что вам не нужны обновления прогресса или предупреждения, когда это делается, в противном случае используйте backgroundworker). Я чувствую, что ты слишком усложняешь это. Редактирование моего кода, чтобы отразить этот

EDIT 2: Вот пример BackgroundWorker подхода для тех, кто заботится

 BackgroundWorker bw = new BackgroundWorker(); 
     bw.DoWork += BwOnDoWork; 
     bw.ProgressChanged += BwOnProgressChanged; 
     bw.RunWorkerCompleted += BwOnRunWorkerCompleted; 
     bw.WorkerSupportsCancellation = true; 
     bw.WorkerReportsProgress = true; 

     //This line here is what starts the asynchronous work 
     bw.RunWorkerAsync(); 



    private void BwOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs) 
    { 
     //Do whatever you want to do when it is done with its asynchronous task 
     //for example 
     Label.Content = "Yay, Were done doing whatever it was that we were doing!!!!" 
    } 

    private void BwOnProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     //Here is where we can send progress reports to the UI, like updating a loading bar 
     MyProgressBar.EditValue = e.ProgressPercentage; 

    } 

    private void BwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs) 
    { 
     //This is where we will put anything we want to be ran asynchronously 
     this.RunWhateverMethodWillDoABunchOfStuff() 
    } 
+0

Большая проблема заключается в том, что за кулисами происходит много операций с базами данных и FTP.Пользовательский интерфейс должен быть текучим, и да, есть некоторые уведомления, которые продолжаются. Я бы использовал фона рабочего, но они выполняют одно и то же; просто async ... более чистый (я думаю?), и я действительно пытаюсь ознакомиться с ним. Это более двух лет, и я продолжаю 3, и я слышал об этом только за последние полгода ... – Will

+0

@ Из всего, что я знаю, будет, BackgroundWorker - это путь, который нужно делать, когда дело касается уведомлений пользовательского интерфейса во время асинхронного использования , у них также есть хорошая встроенная функциональность, и я лично считаю их очень легко работать. Однако у меня нет опыта работы с 'async', поэтому я, к сожалению, не могу взвесить. – psoshmo

+0

Спасибо за помощь; Я работал с BGW раньше, но, чтобы переключиться с асинхронного на BGW (учитывая, насколько далеко продвинуто приложение), я бы отбросил работу в ненужной манере. – Will

Смежные вопросы