Câu trả lời:
Bạn không thể có các phương thức async với refhoặc outtham số.
Lucian Wischik giải thích lý do tại sao điều này không thể thực hiện được trên luồng MSDN này: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cann -ref-hoặc-out-tham số
Đối với lý do tại sao các phương thức async không hỗ trợ các tham số ngoài tham chiếu? (hoặc tham số ref?) Đó là giới hạn của CLR. Chúng tôi đã chọn thực hiện các phương thức async theo cách tương tự như các phương thức lặp - tức là thông qua trình biên dịch chuyển đổi phương thức thành một đối tượng máy-trạng thái. CLR không có cách nào an toàn để lưu địa chỉ của "tham số out" hoặc "tham số tham chiếu" dưới dạng trường của một đối tượng. Cách duy nhất để có các tham số ngoài tham chiếu được hỗ trợ là nếu tính năng async được thực hiện bằng cách viết lại CLR cấp thấp thay vì viết lại trình biên dịch. Chúng tôi đã kiểm tra cách tiếp cận đó, và nó đã có rất nhiều thứ cho nó, nhưng cuối cùng nó sẽ tốn kém đến mức nó không bao giờ xảy ra.
Một cách giải quyết điển hình cho tình huống này là phương thức async trả về Tuple thay thế. Bạn có thể viết lại phương thức của bạn như vậy:
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);
}
              Tuplethay thế. Rất hữu ích.
                    Tuple. : P
                    Bạn không thể có refhoặc outtham số trong asynccác phương thức (như đã được ghi chú).
Điều này hét lên cho một số mô hình trong dữ liệu di chuyển xung quanh:
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;
}
Bạn có khả năng sử dụng lại mã của mình dễ dàng hơn, cộng với đó là cách dễ đọc hơn các biến hoặc bộ dữ liệu.
Giải pháp C # 7 + là sử dụng cú pháp tuple ẩn.
    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."
        }));
    }
kết quả trả về sử dụng tên thuộc tính chữ ký phương thức xác định. ví dụ:
var foo = await TryLogin(request);
if (foo.IsSuccess)
     return foo.Result;
              Alex đã làm cho một điểm tuyệt vời về khả năng đọc. Tương tự, một hàm cũng đủ giao diện để xác định (các) loại được trả về và bạn cũng nhận được các tên biến có ý nghĩa.
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
    bool canGetData = true;
    if (canGetData) callback(5);
    return Task.FromResult(canGetData);
}
Người gọi cung cấp lambda (hoặc một chức năng được đặt tên) và trợ giúp bằng cách sao chép (các) tên biến từ đại biểu.
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
Cách tiếp cận cụ thể này giống như một phương pháp "Thử" trong đó myOpđược đặt nếu kết quả phương thức là true. Nếu không, bạn không quan tâm myOp.
Một tính năng hay của các outtham số là chúng có thể được sử dụng để trả về dữ liệu ngay cả khi hàm ném ngoại lệ. Tôi nghĩ rằng tương đương gần nhất để làm điều này với một asyncphương thức sẽ là sử dụng một đối tượng mới để giữ dữ liệu mà cả asyncphương thức và người gọi có thể tham chiếu. Một cách khác là thông qua một đại biểu như được đề xuất trong câu trả lời khác .
Lưu ý rằng cả hai kỹ thuật này sẽ không có bất kỳ loại thực thi nào từ trình biên dịch outcó. Tức là, trình biên dịch sẽ không yêu cầu bạn đặt giá trị trên đối tượng được chia sẻ hoặc gọi một đại biểu được thông qua.
Dưới đây là một triển khai ví dụ sử dụng một đối tượng được chia sẻ để bắt chước refvà outsử dụng với asynccác phương thức và các tình huống khác nhau có sẵn refvà outkhông có sẵn:
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.");
    }
}
              Tôi yêu các Trymẫu. Đó là một mô hình gọn gàng.
if (double.TryParse(name, out var result))
{
    // handle success
}
else
{
    // handle error
}
Nhưng, đó là thử thách với async. Điều đó không có nghĩa là chúng ta không có lựa chọn thực sự. Dưới đây là ba cách tiếp cận cốt lõi mà bạn có thể xem xét cho asynccác phương thức trong một phiên bản gần đúng của Trymẫu.
Điều này trông giống như một Tryphương thức đồng bộ chỉ trả về một tuplethay vì boolvới một outtham số, mà tất cả chúng ta đều biết là không được phép trong C #.
var result = await DoAsync(name);
if (result.Success)
{
    // handle success
}
else
{
    // handle error
}
Với một phương pháp mà lợi nhuận truecủa falsevà không bao giờ ném một exception.
Hãy nhớ rằng, việc ném một ngoại lệ trong một
Tryphương thức sẽ phá vỡ toàn bộ mục đích của mẫu.
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);
    }
}
Chúng ta có thể sử dụng anonymouscác phương thức để đặt các biến ngoài. Đó là cú pháp thông minh, mặc dù hơi phức tạp. Với liều lượng nhỏ, nó ổn.
var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
    // handle success
}
else
{
    // handle failure
}
Phương thức tuân theo các điều cơ bản của Trymẫu nhưng đặt outtham số được truyền trong các phương thức gọi lại. Nó được thực hiện như thế này.
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;
    }
}
Có một câu hỏi trong đầu tôi về hiệu suất ở đây. Nhưng, trình biên dịch C # rất thông minh, đến nỗi tôi nghĩ rằng bạn an toàn khi chọn tùy chọn này, gần như chắc chắn.
Điều gì nếu bạn chỉ sử dụng TPLnhư thiết kế? Không có tuple. Ý tưởng ở đây là chúng tôi sử dụng các ngoại lệ để chuyển hướng ContinueWithđến hai đường dẫn khác nhau.
await DoAsync(name).ContinueWith(task =>
{
    if (task.Exception != null)
    {
        // handle fail
    }
    if (task.Result is StorageFile sf)
    {
        // handle success
    }
});
Với một phương pháp ném một exceptionkhi có bất kỳ loại thất bại. Điều đó khác với việc trở về a boolean. Đó là một cách để giao tiếp với TPL.
async Task<StorageFile> DoAsync(string fileName)
{
    var folder = ApplicationData.Current.LocalCacheFolder;
    return await folder.GetFileAsync(fileName);
}
Trong đoạn mã trên, nếu không tìm thấy tệp, một ngoại lệ sẽ được đưa ra. Điều này sẽ gọi thất bại ContinueWithsẽ xử lý Task.Exceptiontrong khối logic của nó. Gọn gàng
Nghe này, có một lý do chúng tôi yêu
Trymẫu. Về cơ bản, nó rất gọn gàng và dễ đọc và kết quả là có thể duy trì được. Khi bạn chọn cách tiếp cận của bạn, cơ quan giám sát cho dễ đọc. Hãy nhớ nhà phát triển tiếp theo trong 6 tháng và bạn không phải trả lời các câu hỏi làm rõ. Mã của bạn có thể là tài liệu duy nhất mà nhà phát triển sẽ có.
May mắn nhất.
ContinueWithcác cuộc gọi xích có kết quả như mong đợi không? Theo sự hiểu biết của tôi, lần thứ hai ContinueWithsẽ kiểm tra sự thành công của lần tiếp theo đầu tiên, chứ không phải sự thành công của nhiệm vụ ban đầu.
                    Tôi gặp vấn đề tương tự như tôi thích khi sử dụng mẫu Phương thức thử mà về cơ bản dường như không tương thích với mô hình không đồng bộ ...
Quan trọng đối với tôi là tôi có thể gọi phương thức Thử trong một mệnh đề if duy nhất và không phải xác định trước các biến ngoài trước, nhưng có thể thực hiện theo dòng như trong ví dụ sau:
if (TryReceive(out string msg))
{
    // use msg
}
Vì vậy, tôi đã đưa ra giải pháp sau đây:
Xác định cấu trúc trợ giúp:
 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);
 }
Xác định async Phương thức thử như thế này:
 public async Task<AsyncOut<bool, string>> TryReceiveAsync()
 {
     string message;
     bool success;
     // ...
     return (success, message);
 }
Gọi phương thức Thử async như thế này:
 if ((await TryReceiveAsync()).Out(out string msg))
 {
     // use msg
 }
Đối với nhiều tham số ngoài, bạn có thể xác định các cấu trúc bổ sung (ví dụ AsyncOut <T, OUT1, OUT2>) hoặc bạn có thể trả về một tuple.
Giới hạn của các asyncphương thức không chấp nhận outtham số chỉ áp dụng cho các phương thức không đồng bộ do trình biên dịch tạo, các phương thức này được khai báo bằng asynctừ khóa. Nó không áp dụng cho các phương thức async thủ công. Nói cách khác, có thể tạo Taskcác phương thức trả về chấp nhận outtham số. Ví dụ: giả sử rằng chúng ta đã có một ParseIntAsyncphương thức ném và chúng ta muốn tạo một phương thức TryParseIntAsynckhông ném. Chúng ta có thể thực hiện nó như thế này:
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);
}
Sử dụng TaskCompletionSourcevà các ContinueWithphương pháp là một chút khó khăn, nhưng không có lựa chọn nào khác vì chúng ta không thể sử dụng thuận tiện awaittừ khóa bên trong phương pháp này.
Ví dụ sử dụng:
if (await TryParseIntAsync("-13", out var result))
{
    Console.WriteLine($"Result: {await result}");
}
else
{
    Console.WriteLine($"Parse failed");
}
Cập nhật: Nếu logic async quá phức tạp để được thể hiện mà không có await, thì nó có thể được gói gọn bên trong một đại biểu ẩn danh không đồng bộ lồng nhau. A TaskCompletionSourcevẫn sẽ cần cho outtham số. Có thể outtham số có thể được hoàn thành trước khi hoàn thành nhiệm vụ chính, như trong ví dụ dưới đây:
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);
    }))();
}
Ví dụ này giả định sự tồn tại của ba phương pháp không đồng bộ GetResponseAsync, GetRawDataAsyncvà FilterDataAsyncđược gọi là liên tiếp. Các outtham số được hoàn thành về việc hoàn thành các phương pháp thứ hai. Các GetDataAsyncphương pháp có thể được sử dụng như thế này:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
Chờ đợi datatrước khi chờ đợi rawDataLengthlà rất quan trọng trong ví dụ đơn giản này, bởi vì trong trường hợp ngoại lệ, outtham số sẽ không bao giờ được hoàn thành.
Tôi nghĩ rằng sử dụng ValueTuples như thế này có thể hoạt động. Trước tiên, bạn phải thêm gói ValueTuple NuGet:
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):
}
              Đây là mã của câu trả lời của @ dcastro được sửa đổi cho C # 7.0 với bộ giải mã được đặt tên và bộ giải mã tuple, giúp hợp lý hóa ký hiệu:
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);
}
Để biết chi tiết về các tuple mới có tên, tuple nghĩa đen và giải mã tuple, hãy xem: https://bloss.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
Bạn có thể làm điều này bằng cách sử dụng TPL (thư viện song song tác vụ) thay vì sử dụng trực tiếp từ khóa đang chờ.
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