Yanıtlar:
refVeya outparametresiyle eşzamansız yöntemleriniz olamaz .
Lucian Wischik, bunun bu MSDN iş parçacığında neden mümkün olmadığını açıklıyor: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have rEF-ya-dışı parametreleri
Neden zaman uyumsuz yöntemler referans dışı parametreleri desteklemiyor? (veya ref parametreleri?) Bu CLR'nin bir sınırlamasıdır. Eşzamansız yöntemleri yineleyici yöntemlere benzer şekilde uygulamayı seçtik - yani yöntemi bir durum-makine-nesnesine dönüştüren derleyici aracılığıyla. CLR'nin bir "out parametresi" veya "referans parametresi" adresini bir nesnenin alanı olarak saklamak için güvenli bir yolu yoktur. Destek dışı parametreleri desteklemenin tek yolu, eşzamansız özelliğin bir derleyici yeniden yazma yerine düşük düzeyli bir CLR yeniden yazma işlemi tarafından yapılmasıdır. Bu yaklaşımı inceledik ve bunun için çok şey vardı, ama sonuçta o kadar maliyetli olacaktı ki asla gerçekleşmeyecekti.
Bu durum için tipik bir çözüm, bunun yerine async yönteminin bir Tuple döndürmesini sağlamaktır. Yönteminizi şu şekilde yeniden yazabilirsiniz:
public async Task Method1()
{
var tuple = await GetDataTaskAsync();
int op = tuple.Item1;
int result = tuple.Item2;
}
public async Task<Tuple<int, int>> GetDataTaskAsync()
{
//...
return new Tuple<int, int>(1, 2);
}
TupleAlternatif için teşekkürler . Çok yararlı.
Tuple. : P
Sen olamaz refya outparametreler asyncyöntemlerle (zaten belirtildiği gibi).
Bu, hareket eden verilerde bazı modellemeler için bağırır:
public class Data
{
public int Op {get; set;}
public int Result {get; set;}
}
public async void Method1()
{
Data data = await GetDataTaskAsync();
// use data.Op and data.Result from here on
}
public async Task<Data> GetDataTaskAsync()
{
var returnValue = new Data();
// Fill up returnValue
return returnValue;
}
Kodunuzu daha kolay bir şekilde yeniden kullanma olanağına sahip olursunuz, ayrıca değişkenlerden veya gruplardan daha okunabilir.
C # 7 + Çözümü örtülü grup sözdizimini kullanmaktır.
private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
{
return (true, BadRequest(new OpenIdErrorResponse
{
Error = OpenIdConnectConstants.Errors.AccessDenied,
ErrorDescription = "Access token provided is not valid."
}));
}
sonuç döndürme imzası tanımlı özellik adlarını kullanır. Örneğin:
var foo = await TryLogin(request);
if (foo.IsSuccess)
return foo.Result;
Alex okunabilirliğe büyük önem verdi. Aynı şekilde, bir işlev de döndürülmekte olan tür (ler) i tanımlamak için yeterince arabirimdir ve anlamlı değişken adları da alırsınız.
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
bool canGetData = true;
if (canGetData) callback(5);
return Task.FromResult(canGetData);
}
Arayanlar bir lambda (veya adlandırılmış bir işlev) sağlar ve intellisense, değişken adlarını temsilciden kopyalayarak yardımcı olur.
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
Bu özel yaklaşım, " myOpSonuç " yöntemi gibidir; burada yöntem sonucu ise ayarlanır true. Aksi takdirde, umurunda değil myOp.
outParametrelerin güzel bir özelliği, bir işlev bir istisna attığında bile veri döndürmek için kullanılabilmeleridir. Ben bir asyncyöntemle bunu yapmak için en yakın eşdeğer asyncyöntem ve arayan başvurabilir veri tutmak için yeni bir nesne kullanmak olacağını düşünüyorum . Başka bir yol , başka bir cevapta önerildiği gibi bir delege geçmek olacaktır .
Bu tekniklerin hiçbirinin sahip olduğu derleyiciden herhangi bir tür uygulamaya sahip olmayacağını unutmayın out. Yani, derleyici, paylaşılan nesne üzerindeki değeri ayarlamanıza veya aktarılan bir temsilci çağırmanıza gerek duymaz.
İşte imitate için paylaşılan nesnesini kullanarak bir örnek uygulama refve outbirlikte kullanılmak üzere asyncyöntem ve diğer çeşitli senaryolar refve outkullanılamaz:
class Ref<T>
{
// Field rather than a property to support passing to functions
// accepting `ref T` or `out T`.
public T Value;
}
async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
var things = new[] { 0, 1, 2, };
var i = 0;
while (true)
{
// Fourth iteration will throw an exception, but we will still have
// communicated data back to the caller via successfulLoopsRef.
things[i] += i;
successfulLoopsRef.Value++;
i++;
}
}
async Task UsageExample()
{
var successCounterRef = new Ref<int>();
// Note that it does not make sense to access successCounterRef
// until OperationExampleAsync completes (either fails or succeeds)
// because there’s no synchronization. Here, I think of passing
// the variable as “temporarily giving ownership” of the referenced
// object to OperationExampleAsync. Deciding on conventions is up to
// you and belongs in documentation ^^.
try
{
await OperationExampleAsync(successCounterRef);
}
finally
{
Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
}
}
TryDeseni seviyorum . Düzenli bir model.
if (double.TryParse(name, out var result))
{
// handle success
}
else
{
// handle error
}
Ancak, bu zor async. Bu, gerçek seçeneklerimiz olmadığı anlamına gelmez. asyncDesenin yarı versiyonundaki yöntemler için düşünebileceğiniz üç temel yaklaşım şunlardır Try.
Bu, çoğu zaman C # 'de izin verilmediğini bildiğimiz bir parametreyle yerine bir Trydöndüren bir senkronizasyon yöntemine benziyor .tupleboolout
var result = await DoAsync(name);
if (result.Success)
{
// handle success
}
else
{
// handle error
}
Döner bir yöntemle trueait falseve asla bir atar exception.
Unutmayın, bir
Tryyönteme istisna atmak , desenin tüm amacını kırar.
async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
return (true, await folder.GetFileAsync(fileName), null);
}
catch (Exception exception)
{
return (false, null, exception);
}
}
anonymousDış değişkenleri belirlemek için yöntemler kullanabiliriz . Zeki sözdizimi biraz karmaşık olsa da. Küçük dozlarda, sorun değil.
var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
// handle success
}
else
{
// handle failure
}
Yöntem, Trydesenin temellerine uyar, ancak outgeri arama yöntemlerinde iletilecek parametreleri ayarlar . Bu böyle yapılır.
async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
file?.Invoke(await folder.GetFileAsync(fileName));
return true;
}
catch (Exception exception)
{
error?.Invoke(exception);
return false;
}
}
Aklımda burada performansla ilgili bir soru var. Ancak, C # derleyicisi o kadar akıllı ki, bu seçeneği seçerken neredeyse güvende olduğunuzu düşünüyorum.
Ya sadece TPLtasarlandığı gibi kullanırsanız ? Tuples yok. Buradaki fikir ContinueWith, iki farklı yola yönlendirmek için istisnalar kullanmamızdır .
await DoAsync(name).ContinueWith(task =>
{
if (task.Exception != null)
{
// handle fail
}
if (task.Result is StorageFile sf)
{
// handle success
}
});
exceptionHerhangi bir hata olduğunda bir zaman atan bir yöntemle . Bu, a döndürmekten farklıdır boolean. İle iletişim kurmanın bir yolu TPL.
async Task<StorageFile> DoAsync(string fileName)
{
var folder = ApplicationData.Current.LocalCacheFolder;
return await folder.GetFileAsync(fileName);
}
Yukarıdaki kodda, dosya bulunamazsa bir istisna atılır. Bu , mantık bloğunda ContinueWithişlenecek hatayı başlatır Task.Exception. Düzgün, ha?
Dinle,
Trykalıbı sevmemizin bir nedeni var . Temelde çok temiz ve okunabilir ve sonuç olarak bakım yapılabilir. Yaklaşımınızı seçerken, okunabilirlik için bekçi köpeği. 6 ay içinde açıklayıcı soruları cevaplamanıza gerek olmayan bir sonraki geliştiriciyi hatırlayın. Kodunuz, bir geliştiricinin sahip olacağı tek belge olabilir.
İyi şanslar.
ContinueWithçağrıları zincirlemenin beklenen sonuca sahip olduğundan emin misiniz ? Anladığım kadarıyla, ikincisi ContinueWith, ilk görevin başarısını değil, orijinal görevin başarısını kontrol edecektir.
Temelde async-await-paradigma ile uyumsuz gibi görünen Try-method-desenini kullanmak gibi aynı sorun vardı ...
Benim için önemli olan, Try-yöntemini tek bir if-cümlesi içinde çağırabiliyorum ve daha önce out değişkenlerini önceden tanımlamak zorunda değilim, ancak aşağıdaki örnekte olduğu gibi satır içi yapabilirim:
if (TryReceive(out string msg))
{
// use msg
}
Bu yüzden aşağıdaki çözümü buldum:
Bir yardımcı yapı tanımlayın:
public struct AsyncOut<T, OUT>
{
private readonly T returnValue;
private readonly OUT result;
public AsyncOut(T returnValue, OUT result)
{
this.returnValue = returnValue;
this.result = result;
}
public T Out(out OUT result)
{
result = this.result;
return returnValue;
}
public T ReturnValue => returnValue;
public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) =>
new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
}
Async Try-yöntemini şu şekilde tanımlayın:
public async Task<AsyncOut<bool, string>> TryReceiveAsync()
{
string message;
bool success;
// ...
return (success, message);
}
Async Try-yöntemini şöyle çağırın:
if ((await TryReceiveAsync()).Out(out string msg))
{
// use msg
}
Çoklu çıkış parametreleri için ek yapılar tanımlayabilirsiniz (örn. AsyncOut <T, OUT1, OUT2>) veya bir demet döndürebilirsiniz.
Parametreleri asynckabul etmeyen yöntemlerin sınırlandırılması outyalnızca derleyici tarafından oluşturulan ve bunlar asyncanahtar sözcükle bildirilen zaman uyumsuz yöntemler için geçerlidir . El yapımı asenkron yöntemler için geçerli değildir. Başka bir deyişle, parametreleri Taskkabul eden geri dönen yöntemler oluşturmak mümkündür out. Örneğin, zaten birParseIntAsync fırlatma yöntemimiz olduğunu ve fırlatmayan yöntem oluşturmak istediğimizi TryParseIntAsyncvarsayalım. Bunu şu şekilde uygulayabiliriz:
public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
var tcs = new TaskCompletionSource<int>();
result = tcs.Task;
return ParseIntAsync(s).ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception.InnerException);
return false;
}
tcs.SetResult(t.Result);
return true;
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
}
Kullanmak TaskCompletionSourceVe ContinueWithyöntemini biraz gariptir, ancak awaitbu yöntemin içinde uygun anahtar kelimeyi kullanamadığımız için başka bir seçenek yoktur .
Kullanım örneği:
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Result: {await result}");
}
else
{
Console.WriteLine($"Parse failed");
}
Güncelleme:await Eşzamansız mantık olmadan ifade edilemeyecek kadar karmaşıksa , o zaman iç içe bir eşzamansız anonim delege içinde kapsüllenebilir. Parametre TaskCompletionSourceiçin hala A gereklidir out. Aşağıdaki outörnekte olduğu gibi , parametrenin ana görevin tamamlanmasından önce tamamlanması mümkündür :
public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
var tcs = new TaskCompletionSource<int>();
rawDataLength = tcs.Task;
return ((Func<Task<string>>)(async () =>
{
var response = await GetResponseAsync(url);
var rawData = await GetRawDataAsync(response);
tcs.SetResult(rawData.Length);
return await FilterDataAsync(rawData);
}))();
}
Bu örnek, üç eşzamansız yöntemin varlığını varsayar GetResponseAsync , GetRawDataAsyncve FilterDataAsyncbu arka arkaya denir. outParametresi ikinci yöntemin tamamlanmasından tamamlanır. GetDataAsyncYöntem bu gibi kullanılabilir:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
Bekliyor data önce beklemek rawDataLengthbu basitleştirilmiş örnekte önemlidir, çünkü bir istisna durumunda outparametre asla tamamlanmayacaktır.
Bunun gibi ValueTuples kullanarak işe yarayabilir düşünüyorum. Yine de ValueTuple NuGet paketini eklemelisiniz:
public async void Method1()
{
(int op, int result) tuple = await GetDataTaskAsync();
int op = tuple.op;
int result = tuple.result;
}
public async Task<(int op, int result)> GetDataTaskAsync()
{
int x = 5;
int y = 10;
return (op: x, result: y):
}
@ Dcastro'nun C # 7.0 için değiştirilmiş ve tuples ve tuple yapısökümüyle değiştirilmiş yanıtı gösterimi düzenleyen kod kodu:
public async void Method1()
{
// Version 1, named tuples:
// just to show how it works
/*
var tuple = await GetDataTaskAsync();
int op = tuple.paramOp;
int result = tuple.paramResult;
*/
// Version 2, tuple deconstruction:
// much shorter, most elegant
(int op, int result) = await GetDataTaskAsync();
}
public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
//...
return (1, 2);
}
Yeni adlandırılmış tuples, tuple değişmezleri ve tuple yapısökümleri hakkında ayrıntılar için bkz. Https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
Bunu, doğrudan await anahtar sözcüğünü kullanmak yerine TPL (görev paralel kitaplığı) kullanarak yapabilirsiniz.
private bool CheckInCategory(int? id, out Category category)
{
if (id == null || id == 0)
category = null;
else
category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;
return category != null;
}
if(!CheckInCategory(int? id, out var category)) return error