2015-04-28 11 views
9

का परीक्षण करना मैं बहुत गुगल कर रहा हूं, और यहां तक ​​कि बिंग-आईएनजी और मैं संतुष्ट होने वाली किसी भी चीज़ के साथ नहीं आया हूं।एसिंक आईसीओएमएंड

मेरे पास एक व्यूमोडेल है जिसमें कुछ कमांड हैं, जैसे कि SaveCommand और NewCommand और DeleteCommand। मेरा SaveCommand एक फ़ाइल में एक सेव ऑपरेशन निष्पादित करता है, जिसे मैं एसिंक ऑपरेशन करना चाहता हूं ताकि यूआई इसके लिए इंतजार न करे।

मेरा SaveCommand AsyncCommand का एक उदाहरण है, जो आईसीओएमएंड लागू करता है।

SaveCommand = new AsyncCommand(
    async param => 
     { 
      Connection con = await Connection.GetInstanceAsync(m_configurationPath); 
      con.Shoppe.Configurations = new List<CouchDbConfig>(m_configurations); 
      await con.SaveConfigurationAsync(m_configurationPath); 
      //now that its saved, we reload the Data. 
      await LoadDataAsync(m_configurationPath); 
     }, 
...etc 

अब मैं अपने व्यूमोडेल के लिए एक परीक्षण तैयार कर रहा हूं। इसमें, मैं न्यूकॉमैंड के साथ एक नई चीज बनाता हूं और फिर मैं इसे संशोधित करता हूं, और फिर SaveCommand का उपयोग करता हूं।

vm.SaveCommand.Execute(null); 
Assert.IsFalse(vm.SaveCommand.CanExecute(null)); 

SaveCommand की मेरी CanExecute विधि (दिखाया नहीं गया) आइटम सहेजे जाने के ठीक बाद झूठी वापसी करनी चाहिए। (एक अपरिवर्तित आइटम को सहेजने का कोई मतलब नहीं है)। हालांकि, ऊपर दिखाए गए आवेषण हर समय विफल रहता है क्योंकि मैं SaveCommand को निष्पादित करने की प्रतीक्षा नहीं कर रहा हूं।

अब, मैं इसे निष्पादित करने के लिए इंतजार नहीं कर सकता क्योंकि मैं नहीं कर सकता। ICommand.Execute एक कार्य वापस नहीं करता है। और यदि मैं AsyncCommand को अपने निष्पादन को एक कार्य वापस करने के लिए बदलता हूं तो यह आईसीओएमएंड इंटरफ़ेस को ठीक से लागू नहीं करेगा।

तो, केवल एक ही चीज़ मुझे लगता है कि मैं अब परीक्षण प्रयोजनों के लिए कर सकते हैं,, AsynCommand एक नया कार्य करने के लिए के लिए,

public async Task ExecuteAsync(object parm) { ... } 

और इस तरह, अपने परीक्षण चलेगा (और इंतजार) ExecuteAsync है फ़ंक्शन, और एक्सएएमएल यूआई आईसीओएमएंड.एक्सक्यूट विधि चलाएगा, जिसमें यह इंतजार नहीं कर रहा है।

मुझे लगता है कि मेरी प्रस्तावित समाधान विधि करने के बारे में मुझे कोई खुशी नहीं है, और उम्मीद है कि एक बेहतर तरीका है।

क्या मैं सुझाव देता हूं, उचित? क्या कोई बेहतर तरीका है?

+0

आप अपने आदेश है कि कहते हैं, "चल रहा है" या "निष्पादित" में किसी भी गुण है? शायद एक तरीका आदेश में एक निष्पादन ध्वज जोड़ने के लिए है। यह भी ठीक हो सकता है कि परीक्षण विफल क्यों होता है, आप यह सुनिश्चित करने के लिए CanExecute में निष्पादन ध्वज का उपयोग कर सकते हैं कि वे दो बार आदेश नहीं चलाते हैं। –

+0

मैं देखता हूं कि आपका क्या मतलब है। मुझे इसे आज़माएं। * बैकसून –

+0

वास्तव में, AsyncCommand में पहले से ही कार्यकारी ध्वज है। जब मुझे शुरुआत में समस्या मिली, टीबीएच, मैं AsyncCommand का उपयोग नहीं कर रहा था, बल्कि एक और चीज जो नहीं थी। शायद मुझे वास्तव में कोई समस्या नहीं है! .. परीक्षण –

उत्तर

2

ऐसा लगता है कि उत्तर AsyncCommand ऑब्जेक्ट के साथ ध्वज का उपयोग कर रहा है। विधि में AsyncCommand का ध्वज यह सुनिश्चित करेगा कि उपयोगकर्ता अन्य उदाहरण चल रहा है, जबकि कमांड निष्पादित नहीं कर सकता है।

अपने इकाई परीक्षण के साथ इसके अलावा

, आप थोड़ी देर के पाश का उपयोग करके इसे ज़ोर के बाद इंतजार कर सकते हैं:

while (vm.SaveCommand.Executing) ; 

ताकि परीक्षण सफाई से बाहर निकालता है।

+0

तो क्या आपका मतलब है कि यह एक और तरीका जोड़ने से क्लीनर है जो 'कार्य' देता है? –

+0

हां, यदि इसका मतलब है कि 'कार्य' का उपयोग केवल परीक्षण उद्देश्यों के लिए किया जाता है। मैं एक परीक्षण पास करने के लिए अतिरिक्त कोड लिखने से बचूंगा, क्योंकि यह एक परीक्षण के उद्देश्य को हरा देता है। –

+0

मुझे लगता है कि यह क्लीनर है। चूंकि दो भ्रमित निष्पादन विधियां नहीं हैं। –

11

सुझाव क्या उचित है, और वास्तव में क्या AsyncCommand implementation created by Stephen Cleary करता

यहाँ लेख से कोड की एक पूरी कार्यान्वयन है (वह सबसे पहले experts on the subject of async कोड IMHO में से एक है) है (प्लस कुछ बदलाव मैंने बनाया उपयोग के मामले के लिए मैं उपयोग कर रहा था।)

AsyncCommand.cs

/* 
* Based on the article: Patterns for Asynchronous MVVM Applications: Commands 
* http://msdn.microsoft.com/en-us/magazine/dn630647.aspx 
* 
* Modified by Scott Chamberlain 11-19-2014 
* - Added parameter support 
* - Added the ability to shut off the single invocation restriction. 
* - Made a non-generic version of the class that called the generic version with a <object> return type. 
*/ 
using System; 
using System.ComponentModel; 
using System.Runtime.CompilerServices; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Input; 

namespace Infrastructure 
{ 
    public class AsyncCommand : AsyncCommand<object> 
    { 
     public AsyncCommand(Func<object, Task> command) 
      : base(async (parmater, token) => { await command(parmater); return null; }, null) 
     { 
     } 

     public AsyncCommand(Func<object, Task> command, Func<object, bool> canExecute) 
      : base(async (parmater, token) => { await command(parmater); return null; }, canExecute) 
     { 
     } 

     public AsyncCommand(Func<object, CancellationToken, Task> command) 
      : base(async (parmater, token) => { await command(parmater, token); return null; }, null) 
     { 
     } 

     public AsyncCommand(Func<object, CancellationToken, Task> command, Func<object, bool> canExecute) 
      : base(async (parmater, token) => { await command(parmater, token); return null; }, canExecute) 
     { 
     } 
    } 

    public class AsyncCommand<TResult> : AsyncCommandBase, INotifyPropertyChanged 
    { 
     private readonly Func<object, CancellationToken, Task<TResult>> _command; 
     private readonly CancelAsyncCommand _cancelCommand; 
     private readonly Func<object, bool> _canExecute; 
     private NotifyTaskCompletion<TResult> _execution; 
     private bool _allowMultipleInvocations; 

     public AsyncCommand(Func<object, Task<TResult>> command) 
      : this((parmater, token) => command(parmater), null) 
     { 
     } 

     public AsyncCommand(Func<object, Task<TResult>> command, Func<object, bool> canExecute) 
      : this((parmater, token) => command(parmater), canExecute) 
     { 
     } 

     public AsyncCommand(Func<object, CancellationToken, Task<TResult>> command) 
      : this(command, null) 
     { 
     } 

     public AsyncCommand(Func<object, CancellationToken, Task<TResult>> command, Func<object, bool> canExecute) 
     { 
      _command = command; 
      _canExecute = canExecute; 
      _cancelCommand = new CancelAsyncCommand(); 
     } 


     public override bool CanExecute(object parameter) 
     { 
      var canExecute = _canExecute == null || _canExecute(parameter); 
      var executionComplete = (Execution == null || Execution.IsCompleted); 

      return canExecute && (AllowMultipleInvocations || executionComplete); 
     } 

     public override async Task ExecuteAsync(object parameter) 
     { 
      _cancelCommand.NotifyCommandStarting(); 
      Execution = new NotifyTaskCompletion<TResult>(_command(parameter, _cancelCommand.Token)); 
      RaiseCanExecuteChanged(); 
      await Execution.TaskCompletion; 
      _cancelCommand.NotifyCommandFinished(); 
      RaiseCanExecuteChanged(); 
     } 

     public bool AllowMultipleInvocations 
     { 
      get { return _allowMultipleInvocations; } 
      set 
      { 
       if (_allowMultipleInvocations == value) 
        return; 

       _allowMultipleInvocations = value; 
       OnPropertyChanged(); 
      } 
     } 

     public ICommand CancelCommand 
     { 
      get { return _cancelCommand; } 
     } 

     public NotifyTaskCompletion<TResult> Execution 
     { 
      get { return _execution; } 
      private set 
      { 
       _execution = value; 
       OnPropertyChanged(); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
     { 
      PropertyChangedEventHandler handler = PropertyChanged; 
      if (handler != null) 
       handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 

     private sealed class CancelAsyncCommand : ICommand 
     { 
      private CancellationTokenSource _cts = new CancellationTokenSource(); 
      private bool _commandExecuting; 

      public CancellationToken Token { get { return _cts.Token; } } 

      public void NotifyCommandStarting() 
      { 
       _commandExecuting = true; 
       if (!_cts.IsCancellationRequested) 
        return; 
       _cts = new CancellationTokenSource(); 
       RaiseCanExecuteChanged(); 
      } 

      public void NotifyCommandFinished() 
      { 
       _commandExecuting = false; 
       RaiseCanExecuteChanged(); 
      } 

      bool ICommand.CanExecute(object parameter) 
      { 
       return _commandExecuting && !_cts.IsCancellationRequested; 
      } 

      void ICommand.Execute(object parameter) 
      { 
       _cts.Cancel(); 
       RaiseCanExecuteChanged(); 
      } 

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

      private void RaiseCanExecuteChanged() 
      { 
       CommandManager.InvalidateRequerySuggested(); 
      } 
     } 
    } 
} 

AsyncCommandBase।सीएस

/* 
* Based on the article: Patterns for Asynchronous MVVM Applications: Commands 
* http://msdn.microsoft.com/en-us/magazine/dn630647.aspx 
*/ 
using System; 
using System.Threading.Tasks; 
using System.Windows.Input; 

namespace Infrastructure 
{ 
    public abstract class AsyncCommandBase : IAsyncCommand 
    { 
     public abstract bool CanExecute(object parameter); 

     public abstract Task ExecuteAsync(object parameter); 

     public async void Execute(object parameter) 
     { 
      await ExecuteAsync(parameter); 
     } 

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

     protected void RaiseCanExecuteChanged() 
     { 
      CommandManager.InvalidateRequerySuggested(); 
     } 
    } 
} 

NotifyTaskCompletion.cs

/* 
* Based on the article: Patterns for Asynchronous MVVM Applications: Commands 
* http://msdn.microsoft.com/en-us/magazine/dn630647.aspx 
* 
* Modifed by Scott Chamberlain on 12/03/2014 
* Split in to two classes, one that does not return a result and a 
* derived class that does. 
*/ 

using System; 
using System.ComponentModel; 
using System.Threading.Tasks; 

namespace Infrastructure 
{ 
    public sealed class NotifyTaskCompletion<TResult> : NotifyTaskCompletion 
    { 
     public NotifyTaskCompletion(Task<TResult> task) 
      : base(task) 
     { 
     } 

     public TResult Result 
     { 
      get 
      { 
       return (Task.Status == TaskStatus.RanToCompletion) ? 
        ((Task<TResult>)Task).Result : default(TResult); 
      } 
     } 
    } 

    public class NotifyTaskCompletion : INotifyPropertyChanged 
    { 
     public NotifyTaskCompletion(Task task) 
     { 
      Task = task; 
      if (!task.IsCompleted) 
       TaskCompletion = WatchTaskAsync(task); 
      else 
       TaskCompletion = Task; 
     } 

     private async Task WatchTaskAsync(Task task) 
     { 
      try 
      { 
       await task; 
      } 
      catch 
      { 
       //This catch is intentionally empty, the errors will be handled lower on the "task.IsFaulted" branch. 
      } 
      var propertyChanged = PropertyChanged; 
      if (propertyChanged == null) 
       return; 
      propertyChanged(this, new PropertyChangedEventArgs("Status")); 
      propertyChanged(this, new PropertyChangedEventArgs("IsCompleted")); 
      propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted")); 
      if (task.IsCanceled) 
      { 
       propertyChanged(this, new PropertyChangedEventArgs("IsCanceled")); 
      } 
      else if (task.IsFaulted) 
      { 
       propertyChanged(this, new PropertyChangedEventArgs("IsFaulted")); 
       propertyChanged(this, new PropertyChangedEventArgs("Exception")); 
       propertyChanged(this, new PropertyChangedEventArgs("InnerException")); 
       propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage")); 
      } 
      else 
      { 
       propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted")); 
       propertyChanged(this, new PropertyChangedEventArgs("Result")); 
      } 
     } 

     public Task Task { get; private set; } 
     public Task TaskCompletion { get; private set; } 
     public TaskStatus Status { get { return Task.Status; } } 
     public bool IsCompleted { get { return Task.IsCompleted; } } 
     public bool IsNotCompleted { get { return !Task.IsCompleted; } } 
     public bool IsSuccessfullyCompleted 
     { 
      get 
      { 
       return Task.Status == 
        TaskStatus.RanToCompletion; 
      } 
     } 
     public bool IsCanceled { get { return Task.IsCanceled; } } 
     public bool IsFaulted { get { return Task.IsFaulted; } } 
     public AggregateException Exception { get { return Task.Exception; } } 
     public Exception InnerException 
     { 
      get 
      { 
       return (Exception == null) ? 
        null : Exception.InnerException; 
      } 
     } 
     public string ErrorMessage 
     { 
      get 
      { 
       return (InnerException == null) ? 
        null : InnerException.Message; 
      } 
     } 
     public event PropertyChangedEventHandler PropertyChanged; 
    } 
} 
+0

Wowsers। ऐसा लगता है कि रसोई सिंक समेत सब कुछ! मैं मानता हूं कि रद्द करने सहित एक बहुत ही पूर्ण कार्यान्वयन है, आदि। अब, मेरा सवाल यह है कि, मैं किस उत्तर को सही चिह्नित करता हूं? मेरी तत्काल जरूरतों के लिए, और शायद मुझे बस कुछ समय की आवश्यकता होगी मेरे परीक्षण मामले में लूप। लेकिन मैं समझता हूं कि यह उत्तर सबसे पूर्ण है और आखिरकार मेरी तत्काल समस्या हल हो जाती है और शायद भविष्य में मेरे पास जो कुछ भी होगा। –