Yanıtlar:
ref
Veya out
parametresiyle 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);
}
Tuple
Alternatif için teşekkürler . Çok yararlı.
Tuple
. : P
Sen olamaz ref
ya out
parametreler async
yö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, " myOp
Sonuç " yöntemi gibidir; burada yöntem sonucu ise ayarlanır true
. Aksi takdirde, umurunda değil myOp
.
out
Parametrelerin güzel bir özelliği, bir işlev bir istisna attığında bile veri döndürmek için kullanılabilmeleridir. Ben bir async
yöntemle bunu yapmak için en yakın eşdeğer async
yö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 ref
ve out
birlikte kullanılmak üzere async
yöntem ve diğer çeşitli senaryolar ref
ve out
kullanı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.");
}
}
Try
Deseni 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. async
Desenin 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 Try
döndüren bir senkronizasyon yöntemine benziyor .tuple
bool
out
var result = await DoAsync(name);
if (result.Success)
{
// handle success
}
else
{
// handle error
}
Döner bir yöntemle true
ait false
ve asla bir atar exception
.
Unutmayın, bir
Try
yö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);
}
}
anonymous
Dış 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, Try
desenin temellerine uyar, ancak out
geri 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 TPL
tasarlandığı 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
}
});
exception
Herhangi 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 ContinueWith
işlenecek hatayı başlatır Task.Exception
. Düzgün, ha?
Dinle,
Try
kalı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 async
kabul etmeyen yöntemlerin sınırlandırılması out
yalnızca derleyici tarafından oluşturulan ve bunlar async
anahtar 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 Task
kabul 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 TryParseIntAsync
varsayalı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 TaskCompletionSource
Ve ContinueWith
yöntemini biraz gariptir, ancak await
bu 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 TaskCompletionSource
iç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
, GetRawDataAsync
ve FilterDataAsync
bu arka arkaya denir. out
Parametresi ikinci yöntemin tamamlanmasından tamamlanır. GetDataAsync
Yö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 rawDataLength
bu basitleştirilmiş örnekte önemlidir, çünkü bir istisna durumunda out
parametre 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