我目前正在尝试使用一些异步方法来制作我的应用程序。我所有的 IO 都是通过接口的显式实现完成的,我对如何使操作异步有点困惑。
正如我所看到的,我在实施中有两个选择:
interface IIO
{
void DoOperation();
}
选项1:做一个隐式实现异步并在隐式实现中等待结果。
class IOImplementation : IIO
{
async void DoOperation()
{
await Task.Factory.StartNew(() =>
{
//WRITING A FILE OR SOME SUCH THINGAMAGIG
});
}
#region IIO Members
void IIO.DoOperation()
{
DoOperation();
}
#endregion
}
选项2:异步执行显式实现并等待来自隐式实现的任务。
class IOAsyncImplementation : IIO
{
private Task DoOperationAsync()
{
return new Task(() =>
{
//DO ALL THE HEAVY LIFTING!!!
});
}
#region IIOAsync Members
async void IIO.DoOperation()
{
await DoOperationAsync();
}
#endregion
}
这些实现中的一个是否比另一个更好,或者还有其他我没有想到的方法吗?
这两个选项都不正确。您正在尝试异步实现同步接口。不要那样做。问题是当 DoOperation()
返回时,操作还没有完成。更糟糕的是,如果在操作过程中发生异常(这在 IO 操作中很常见),用户将没有机会处理该异常。
您需要做的是修改接口,使其异步:
interface IIO
{
Task DoOperationAsync(); // note: no async here
}
class IOImplementation : IIO
{
public async Task DoOperationAsync()
{
// perform the operation here
}
}
这样,用户将看到该操作是 async
并且他们将能够对其进行 await
。这也几乎迫使您的代码的用户切换到 async
,但这是不可避免的。
另外,我假设在您的实现中使用 StartNew()
只是一个示例,您不需要它来实现异步 IO。 (而 new Task()
甚至更糟,甚至都行不通,因为您没有 Start()
Task
。)
更好的解决方案是为异步操作引入另一个接口。新接口必须继承原接口。
例子:
interface IIO
{
void DoOperation();
}
interface IIOAsync : IIO
{
Task DoOperationAsync();
}
class ClsAsync : IIOAsync
{
public void DoOperation()
{
DoOperationAsync().GetAwaiter().GetResult();
}
public async Task DoOperationAsync()
{
//just an async code demo
await Task.Delay(1000);
}
}
class Program
{
static void Main(string[] args)
{
IIOAsync asAsync = new ClsAsync();
IIO asSync = asAsync;
Console.WriteLine(DateTime.Now.Second);
asAsync.DoOperation();
Console.WriteLine("After call to sync func using Async iface: {0}",
DateTime.Now.Second);
asAsync.DoOperationAsync().GetAwaiter().GetResult();
Console.WriteLine("After call to async func using Async iface: {0}",
DateTime.Now.Second);
asSync.DoOperation();
Console.WriteLine("After call to sync func using Sync iface: {0}",
DateTime.Now.Second);
Console.ReadKey(true);
}
}
PS 重新设计你的异步操作,让它们返回 Task 而不是 void,除非你真的必须返回 void。
GetAwaiter().GetResult()
而不是 Wait()
?这样您就不需要解压缩 AggregateException
来获取内部异常。
class Impl : IIO, IIOAsync
。然而,IIO 和 IIOAsync 本身是不同的合约,可以避免将“旧合约”引入新代码。 var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c
。
我根据 Svick 的回答创建了一个示例应用程序,发现在没有 async
关键字的情况下调用 IOImplementation.DoOperationAsync()
不会导致编译器/Visual Studio 警告。这是基于 Visual Studio 2019 和 .NET Core 3.1。
下面的示例代码。
public interface ISomething
{
Task DoSomethingAsync();
}
public class Something : ISomething
{
public async Task DoSomethingAsync()
{
await Task.Run(() => Thread.Sleep(2000));
Console.WriteLine("Message from DoSomethingAsync");
throw new Exception("Some exception");
}
}
class Program
{
static void Main(string[] args)
{
ISomething something = new Something();
Console.WriteLine("pre something.DoSomethingAsync() without await");
something.DoSomethingAsync(); // No compiler warning for missing "await" and exception is "swallowed"
Console.WriteLine("post something.DoSomethingAsync() without await");
Thread.Sleep(3000);
// Output:
// pre something.DoSomethingAsync() without await
// post something.DoSomethingAsync() without await
// Message from DoSomethingAsync
}
}
可以使用抽象类代替接口(在 C# 7.3 中)。
// Like interface
abstract class IIO
{
public virtual async Task<string> DoOperation(string Name)
{
throw new NotImplementedException(); // throwing exception
// return await Task.Run(() => { return ""; }); // or empty do
}
}
// Implementation
class IOImplementation : IIO
{
public override async Task<string> DoOperation(string Name)
{
return await await Task.Run(() =>
{
if(Name == "Spiderman")
return "ok";
return "cancel";
});
}
}
IIO.DoOperation()
有一个虚拟实现而不是 abstract
本身?
abstract
成员,则创建类 abstract
是没有意义的。可以将 DoOperation()
方法设为 abstract
,但是您得到的东西本质上只是一个接口。这让人回想起,为什么不直接使用界面呢?上面的代码毫无意义。 :(
abstract async
。将 async
添加到方法声明的唯一作用是允许您在方法中使用 await
。如果您的方法不使用 await
,那么您不需要 async
。这就是接口方法首先起作用的原因。 真正重要的是返回类型。您可以在没有 async
关键字的情况下将返回类型设为 Task<string>
。
不定期副业成功案例分享
async
):async Task IIO.DoOperationAsync()
。你的意思是你在哪里await
返回的Task
?无论您在哪里调用DoOperationAsync()
。Task.Run()
中,该 IO 代码本身应该是异步的,您可以直接使用await
。例如line = await streamReader.ReadLineAsync()
。